项目性能优化 - 并发编程合并文章详情页的 HTTP 请求次数

2023-09-15 13:50:19

目录

1. 原始文章详情页

2. 为什么要使用并发编程升级文章详情页

3. 如何使用并发编程升级文章详情页

3.1 配置线程池

3.2 合并前端请求

3.3 合并后端接口,使用多线程并发执行

4. 项目相关链接


1. 原始文章详情页

【前端代码】

<script type="text/javascript">
        var aid = getURLParam("id"); // 文章ID
        var islogin = false; // 是否登录
        // 初始化文章详情
        var editormd;
        function initEdit(md){
            editormd = editormd.markdownToHTML("editorDiv", {
            markdown : md,
            });
        }

        // 初始化文章的详细信息
        function initArtDetail() {
            jQuery.ajax({
                url:"/art/detail",
                type:"GET",
                data:{
                    "aid":aid
                },
                success:function(body) {
                    if(body.code==200 && body.data!=null && body.data.id>=0) {
                        // 查询成功: body 是 articleinfoVo 对象
                        var art = body.data;
                        jQuery("#title").html(art.title); // 设置文章标题
                        jQuery("#createtime").html(art.createtime); // 设置文章发布时间
                        jQuery("#rcount").html(art.rcount); // 设置文章访问量
                        initEdit(art.content); // 设置文章详情
                        if (art.photo != null && art.photo != "") {
                            jQuery("#photo").attr("src", art.photo); // 设置头像
                        }
                        jQuery("#username").html(art.username); // 设置昵称
                        jQuery("#artcount").html(art.artCount);
                    } else {
                        alert("抱歉: 操作失败, 请重试! ");
                    }
                }
            });
        }

        // [是否要显示评论删除按钮] - 判断当前文章是否属于当前登录用户
        function isArtByMe(aid) {
            jQuery.ajax({
                url: "/user/isartbyme",
                type: "GET",
                data: {
                    "aid": aid
                },
                success: function (res) {
                    if (res.code == 200 && res.data == 1) {
                        // 当前文章归属于当前登录用户
                        jQuery(".comment_del_class").each(function (i) {
                            jQuery(this).show();
                        });
                    }
                }
            });
        }

        // 加载评论列表
        function initComment() {
            jQuery.ajax({
                url:"/comment/list",
                type:"GET",
                data:{
                    "aid":aid
                },
                success:function(body) {
                    if(body.code==200 && body.data!=null) {
                        var commentListHtml = "";
                        for(let comment of body.data) {
                            commentListHtml += '<div style="margin-bottom: 26px;">';
                            commentListHtml += comment.username + ':' + comment.content;
                            commentListHtml += '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <a class="comment_del_class" style="display: none;text-decoration:none; color:grey;" href="javascript:del(' +
                                comment.cid + ')">删除</a>';
                            commentListHtml += '</div>';
                        }
                        jQuery("#commentlist").html(commentListHtml);
                        var commentCount = body.data.length;
                        jQuery("#commentCount").html('共 ' + commentCount + ' 条评论');
                    }
                }
            });
        }

        // 获取当前登录用户的信息
        function getSessionUser() {
            jQuery.ajax({
                url: "/user/myinfo",
                type: "GET",
                data: {},
                success: function (body) {
                    if (body.code == 200 && body.data != null && body.data.id >= 0) {
                        // 当前用户已经登录
                        islogin = true;
                        jQuery("#comment_login_name").html(body.data.username);  // 评论人

                        // 判断当前文章是否是当前登录用户发表的, 如果是就显示删除按钮
                        // 此处如果另起一个ajax生成删除按钮,可能不好处理,因为这几个ajax请求的执行顺序不清除, 很可能
                        // 先执行了生成删除按钮,再拿用户登录信息,这样就不合理
                        isArtByMe(aid); 
                    } else {
                        // 当前用户未登录
                    }
                }
            });
        }

        // 更新访问量
        function updateCount() {
            jQuery.ajax({
                url:"/art/add_rcount",
                type:"POST",
                data:{
                    "aid":aid
                },
                success:function(body) {
                }
            });
        }

        // 总的初始化详情页
        function initPage() {
            // 初始化文章详情页
            initArtDetail();
            // 加载评论列表
            initComment();
            // 更新访问量
            updateCount();
            // 获取当前登录用户的信息
            getSessionUser();
        }
        initPage();
</script>

前端主要做了这四件事:

  1. 初始化文章详情;
  2. 初始化当前文章底下爱的评论列表;
  3. 请求文章详情页时, 阅读量 + 1;
  4. 获取当前用户的登录信息(是否显示登录人,是否显示删除按钮).

后端  controller  分别写 4 个接口处理相应的请求即可.

2. 为什么要使用并发编程升级文章详情页

        🍁根据前面的前端代码,我们可以看出来,每次访问文章详情页,都会调用 4 个方法,而每一个方法的调用就是一次 HTTP 请求,而 HTTP 请求又是基于 TCP 协议的,所以每一次请求都需要进行 3 次握手,4 次挥手,那么 4 次请求,就需要进行 12 次握手,和 16 次挥手;

        🍁这样肯定是不如发送一次 HTTP 请求的性能高的,只不过合并 4 次请求,会使得建立连接和断开连接的时间比原来长那么一点,但是在计算响应的时候,使用了 4 个线程并发执行的,所以整体来说,性能还是可以提升不少的.

3. 如何使用并发编程升级文章详情页

3.1 配置线程池

@Configuration
public class ThreadPoolConfig {

    @Bean
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setCorePoolSize(5); // 核心线程数
        taskExecutor.setMaxPoolSize(10); // 最大线程数
        taskExecutor.setQueueCapacity(10000); // 任务队列的大小
        taskExecutor.setThreadNamePrefix("my-thread-pool-"); // 前缀名
        taskExecutor.initialize();
        return taskExecutor;
    }
}

3.2 合并前端请求

<script type="text/javascript">
        var aid = getURLParam("id"); // 文章ID
        var islogin = false; // 是否登录
        // 初始化文章详情
        var editormd;
        function initEdit(md){
            editormd = editormd.markdownToHTML("editorDiv", {
            markdown : md,
            });
        }

        // 初始化文章的详细信息
        function initArtDetail(art) {
            if(art == null || art.aid <= 0) {
                alert("参数有误!");
                return false;
            }
            // 查询文章成功
            jQuery("#title").html(art.title); // 设置文章标题
            jQuery("#createtime").html(art.createtime); // 设置文章发布时间
            jQuery("#rcount").html(art.rcount); // 设置文章访问量
            initEdit(art.content); // 设置文章详情
            if (art.photo != null && art.photo != "") {
                jQuery("#photo").attr("src", art.photo); // 设置头像
            }
            jQuery("#username").html(art.username); // 设置昵称
            jQuery("#artcount").html(art.artCount);
        }

        // [是否要显示评论删除按钮] - 判断当前文章是否属于当前登录用户
        function isArtByMe(aid) {
            jQuery.ajax({
                url: "/user/isartbyme",
                type: "GET",
                data: {
                    "aid": aid
                },
                success: function (res) {
                    if (res.code == 200 && res.data == 1) {
                        // 当前文章归属于当前登录用户
                        jQuery(".comment_del_class").each(function (i) {
                            jQuery(this).show();
                        });
                    }
                }
            });
        }

        // 加载评论列表
        function initComment(commentList) {
            var commentListHtml = "";
            for(let comment of commentList) {
                commentListHtml += '<div style="margin-bottom: 26px;">';
                commentListHtml += comment.username + ':' + comment.content;
                commentListHtml += '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <a class="comment_del_class" style="display: none;text-decoration:none; color:grey;" href="javascript:del(' +
                    comment.cid + ')">删除</a>';
                commentListHtml += '</div>';
            }
            jQuery("#commentlist").html(commentListHtml);
            var commentCount = commentList.length;
            jQuery("#commentCount").html('共 ' + commentCount + ' 条评论');
        }

        // 获取当前登录用户的信息
        function getSessionUser(userinfo) {
            if(userinfo != null && userinfo.uid >= 0) {
                // 当前用户已经登录
                islogin = true;
                jQuery("#comment_login_name").html(userinfo.username);  // 评论人

                // 1.判断当前文章是否是当前登录用户发表的, 如果是就显示删除按钮;
                // 2.此处如果另起一个ajax生成删除按钮,可能不好处理,因为这几个ajax请求的执行顺序不清除, 
                // 很可能先执行了生成删除按钮,再拿用户登录信息,这样就不合理.
                isArtByMe(aid); 

            }
        }

        // 总的初始化详情页
        function initPage() {
            jQuery.ajax({
                url:"art/total_init",
                type:"POST",
                data:{
                    "aid":aid
                },
                success:function(body) {
                    if(body.code == 200 && body.data != null)
                    // 初始化文章详情页
                    initArtDetail(res.data.articleinfo);
                    // 加载评论列表
                    initComment(body.data.commentList);
                    // 获取当前登录用户的信息
                    getSessionUser(body.data.userinfo);
                }
            });
        }
        initPage();
</script>

3.3 合并后端接口,使用多线程并发执行

/**
 * 详情页接口初始化合并方法
 * @param aid
 * @return
 */
@RequestMapping("/total_init")
public Object totalInit(Integer aid, HttpServletRequest request) throws ExecutionException, InterruptedException {
    // 1.非空效验
    if(aid == null && aid <= 0) {
        return AjaxResult.fail(-1, "非法参数! ");
    }
    // 2.创建多个任务,放到线程池中进行执行
    // 2.1 得到文章详情
    FutureTask<ArticleInfoVo> getArticleInfo = new FutureTask<>(() -> {
        // 查询数据库中的文章信息
        ArticleInfoVo articleInfoVo = articleService.getDetail(aid);
        if(articleInfoVo != null) {
            // 组装数据: 查询当前用户发表的文章总数
            articleInfoVo.setArtCount(articleService.artCount(articleInfoVo.getUid()));
            // 查询图片
            UserInfo userInfo = userService.getById(articleInfoVo.getUid());
            articleInfoVo.setPhoto(userInfo.getPhoto());
        }
        return articleInfoVo;
    });

    // 2.2 得到当前文章的评论列表
    FutureTask<List<CommentInfoVo>> getCommentList = new FutureTask<>(() -> {
        return commentInfoService.getList(aid);
    });
    // 2.3 给当前文章的阅读量 + 1
    FutureTask<Integer> updateRCount = new FutureTask<>(() -> {
        return articleService.addRCount(aid);
    });
    // 2.4 当前登录用户的信息返回给前端
    FutureTask<UserInfo> getSession = new FutureTask<>(() -> {
        return SessionUtil.getLoginUser(request);
    });
    // 将任务放到线程池中并发执行
    taskExecutor.submit(getArticleInfo);
    taskExecutor.submit(getCommentList);
    taskExecutor.submit(updateRCount); // 只操作后端,不需要等待执行完
    taskExecutor.submit(getSession);
    // 等待任务执行完
    ArticleInfoVo articleInfoVo = getArticleInfo.get(); // 阻塞至执行完
    List<CommentInfoVo> commentList = getCommentList.get();
    UserInfo userInfo = getSession.get();
    // 3.组装并发执行的结果,返回给前端
    HashMap<String, Object> resultMap = new HashMap<>();
    resultMap.put("articleinfo",articleInfoVo);
    resultMap.put("commentList",commentList);
    resultMap.put("userinfo",userInfo);
    return AjaxResult.success(resultMap);
}

【注意】后端合并接口后,新的接口需要在拦截器里面设置放行,否则分页列表页查看详情页就会没有权限.

4. 项目相关链接

 项目仓库:https://gitee.com/xiaobite_hl/JAVA/tree/master/Big-work/mycnblog_ssm_2

项目博客:https://blog.csdn.net/xaiobit_hl/article/details/128727219 

项目 web 自动化测试: https://blog.csdn.net/xaiobit_hl/article/details/129903816


更多推荐

大模型从入门到应用——LangChain:代理(Agents)-[工具(Tools):人工确认工具验证和Tools作为OpenAI函数]

分类目录:《大模型从入门到应用》总目录LangChain系列文章:基础知识快速入门安装与环境配置链(Chains)、代理(Agent:)和记忆(Memory)快速开发聊天模型模型(Models)基础知识大型语言模型(LLMs)基础知识LLM的异步API、自定义LLM包装器、虚假LLM和人类输入LLM(HumanInpu

Pytorch-YOLOv4梳理——原理和复现

yolov1到yolov3的梳理:YOLO总结,从YOLOv1到YOLOv3_追忆苔上雪的博客-CSDN博客首先说一点,就是yolov4的分支有点多,先梳理一下出现的顺序。AlexeyBochkovskiy提出了YOLOv4然后针对YOLOv4的模型缩放(modelscale),提出了Scaled-YOLOv4Scal

黑马JVM总结(八)

(1)StringTable面试题1.81.6时(2)StringTable的位置jvm1.6时StringTable是常量池的一部分,它随着常量池存储在永久代当中,在1.7、1.8中从永久代变成了堆中,为什么做这个更改呢?因为永久代的内存效率很低,永久代是在FullGC的时候才会触发永久代的垃圾回收,FullGC只有

Archicad 26 for Mac - 打造卓越的3D建模工具

随着建筑设计和规划的日益复杂化,寻找一款功能强大且易于使用的3D建模工具变得至关重要。而Archicad26forMac正是您在建筑设计领域中的理想选择。无论您是一名建筑师、室内设计师还是建筑工程师,Archicad26都将成为您的得力助手。作为一款全球领先的BIM(建筑信息模型)软件,Archicad26forMac

Spring 6.0 新特性

文章目录Spring的发展历史AOTGraalVMSpringBoot实战AOTRuntimeHints案例分析RuntimeHintsRegistrarSpringBoot中AOT核心代码Spring的发展历史AOTSpring6.0的新特性AheadofTime(AOT)编译是一种技术,可以提前将Spring应用程

Jmeter常用线程组设置策略

一、前言​在JMeter压力测试中,我们时常见到的几个场景有:单场景基准测试、单场景并发测试、单场景容量测试、混合场景容量测试、混合场景并发测试以及混合场景稳定性测试在本篇文章中,我们会用到一些插件,在这边先给大家列出:​CustomThreadGroups插件PS:在我们正式测试中,统一使用非GUI界面运行,只有在调

对比接口测试工具在自动化测试优缺点:Jmeter、Python、Postman

一、JMeter总结:适合对代码不敏感的使用人员,不会代码也可以完成接口自动化,设计框架。适合紧急迭代的项目。JMeter接口测试的优势小巧轻量级,并且开源免费,社区接受度高,比较容易入门支持多协议,并提供了比较高级的扩展能力,允许自己定义和扩展新的协议支持,比如扩展支持阿里提供的Dubbo协议的JMeter插件等学习

JMeter 常见函数讲解

当使用JMeter进行性能测试或负载测试时,函数是一个非常有用的工具,可以帮助生成动态的测试数据或处理测试结果。下面是一些常用的JMeter函数的详细讲解和并列示例:1、__threadNum:返回当前线程的编号。可以在测试过程中用于生成唯一的标识符或动态数据。生成唯一的用户名:${__threadNum}-user动

MySQL

数据库分两大类:关系型数据SQL非关系型数据库NoSQL关系型数据库典型代表:MySQLMariaDBPostgreSQL(pgsql)OracleSQLServerDB2国产数据库:阿里云RDB华为高斯阿里Oceanbase腾讯TDBA1.SQLSQL(StructuredQueryLanguage)是具有数据操纵和

Spring-动态代理深入了解

😀前言本篇的Spring-AOP系类文章第二篇扩展了Spring-动态代理然后开发了简易的AOP类🏠个人主页:尘觉主页🧑个人简介:大家好,我是尘觉,希望我的文章可以帮助到大家,您的满意是我的动力😉😉在csdn获奖荣誉:🏆csdn城市之星2名⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣⁣💓

Nginx服务优化措施、网页安全与配置防盗链

目录一.优化Nginx二.隐藏/查看版本号隐藏版本号方法一:修改配置文件,关闭版本号隐藏版本号方法二:修改源码文件中的版本号,重新编译安装三.修改用户与组四.设置缓存时间五.日志切割脚本六.设置连接超时控制连接访问时间七.开启多进程八.配置网页压缩九.配置防盗链9.1.配置web源主机(192.168.47.103)9

热文推荐