博客系统项目

news2025/1/21 18:51:50

md5加盐对用户密码进行加密;
全服用户博客列表页,实现分页查询;
用户博客列表页;
写博客,发博客,改博客;
博客草稿箱,自动保存,定时发布;
博客访问量,博客评论区,博客点赞;

数据库的增删改查

提示:这里可以添加技术整体架构
这个项目的功能几乎就是数据库的增删改查,但是在具体的实现细节上有一些需要注意的地方.
全服用户博客列表页会发送一个ajax异步请求直接查询到articleinfo表里已经发布的文章.
注意我这里使用state字段来定义文章的状态,state=1表示文章已经成功发布 ,state=0表示文章保存在草稿箱里.
为了能够更加便捷的从数据库里查询到文章,在url的query string里拼接上每篇文章的唯一标识id,这样点击标签跳转页面时时,可以直接给服务器发送一个get请求直接从数据库里根据id查询到文章并返回给前端,修改博客,查看博客详情,删除博客都要用到这个id.
在全服用户博客列表页上,将服务器传来的数据动态的拼接到html标签上:

    function initpage() {
          //发送一个ajax请求,获取数据库的全部文章
        jQuery.ajax({
        url: "/art/getartlist",
        type: "Post",
        data:{"pageIndex": pageIndex,"maxSize": maxSize},
        success: function (result){
            //对返回结果进行数据校验
            if(result != null && result.code == 200 && result.data.list.length>0){
                //这个变量用来拼接html标签
                var artListHTML ="";
                for (var i = 0; i<result.data.list.length; i++){
                    //每一篇文章都在list里面
                    var articleinfo = result.data.list[i];
                    //拼标签
                    artListHTML += "<div class='blog'>";
                    artListHTML += "<div class=\"title\">"+articleinfo.title+"</div>";
                    artListHTML += "<div class=\"date\">"+articleinfo.updatetime+"</div>";
                    artListHTML += " <div class=\"desc\">\n" + articleinfo.content+"</div>";
                    artListHTML += " <a href=\"blog_content.html?id=" + articleinfo.id
                        + "\" class=\"detail\">查看全文 &gt;&gt;</a>";

                    artListHTML +=  "</div>"
                }
                //将artListHTML标签添加到父标签 <div class="blog" id="listblog">里面
                jQuery("#listblog").html(artListHTML);


            }
            //获取最大页数
            maxPage = result.data.finalMaxPage;

        }

    })
    }
    initpage();

在这里插入图片描述
在这里插入图片描述
个人列表页同样也是如此:
“查看全文”,“修改”,"删除"都是点击之后跳转url对应的页面,然后发送ajax请求给后端,通过文章id来在数据库里查询/删除id唯一指向的文章
在这里插入图片描述

在这里插入图片描述

草稿箱

在这里插入图片描述
草稿箱里的文章state=0,正式发布的文章state=1
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
响应:
在这里插入图片描述

草稿箱自动保存

自动保存功能主要逻辑集中在前端,我开发这个功能的初心是用户在对于草稿箱里内容可能会忘记保存然后退出去了,这次的修改就作废了。
自动保存本质也只是在页面关闭时,给服务发送ajax请求,修改数据库里id对应的标题和内容,id依然从url里面获取.
思路:
1、我先获取到前端页面的标题输入框和文本输入框,页面加载完成后先将标题输入框和文本输入框的内容存储到浏览器的 localStorage里面;
2、然后添加一个定时器和监听事件,每3s就监听一下标题输和文入框的内容是否和 localStorage里的不同,如果修改了,就又将最新的标题和内容存到 localStorage里,这样 localStorage里会保存最新的内容;
3、用户忘记点保存草稿关掉页面时,在检测到页面要关闭的时候,直接从 localStorage里读取到最新的内容通过ajax发送给服务器保存到数据库里面
4、设置了一个开关,let isManualSave = 0,当用户点击“保存草稿”时,isManualSave++,自动保存在触发前会先判断一下isManualSave的值,如果isManualSave = 0说明用户没有手动点击保存,触发自动保存功能,如果isManualSave !=0说明用户已经手动保存了,不用触发手动保存了;
在这里插入图片描述
自动保存的代码:

<script>
  // 博客数据对象,包括标题和内容
  var blogData = {
    title: "",
    content: ""
  };

  // 获取标题输入框和内容输入框的 DOM 元素
  var titleInput = document.getElementById("title");
  var contentInput = document.getElementById("editor-markdown");

  // 监听标题输入框的输入事件
  titleInput.addEventListener("input", function() {
    autoSave();
  });

  // 监听内容输入框的输入事件
  contentInput.addEventListener("input", function() {
    autoSave();
  });

  // 每隔3秒检查标题和内容是否改变,改变了就自动保存
  setInterval(function() {
    var latestTitle = titleInput.value;
    var latestContent = contentInput.value;

    if (latestTitle !== blogData.title || latestContent !== blogData.content) {
      blogData.title = latestTitle;
      blogData.content = latestContent;
      autoSave();
    }
    console.log("调用了3s检测")
  }, 3000);

  // 自动保存函数
  function autoSave() {
    // 将博客数据保存到浏览器的本地存储
    localStorage.setItem("draftBlog", JSON.stringify(blogData));
    console.log("保存到本地了")
  }



  // 在用户离开页面前,将博客数据发送给服务器
  window.addEventListener("beforeunload", function(event) {

    console.log("页面关闭了")
    var latestData = localStorage.getItem("draftBlog");
    if (latestData) {
      blogData = JSON.parse(latestData);
    }
    var lastTitle = blogData.title
    var lastContent = blogData.content;
    console.log(lastTitle);
    console.log(lastContent)
    draftid = getUrlValue("id");
    if(isManualSave == 0){
      // 2.进行修改操作
      jQuery.ajax({
        url:"/art/update",
        type:"POST",
        data:{"title":lastTitle,"content":lastContent,"id":draftid},
        success:function(result){
          console.log("Ajax request success:", result);
          if(result!=null && result.code==200 && result.data==1){
            // alert("恭喜:修改成功!");
            // location.href = "myblog_list.html";
          }else{
            // alert("抱歉:操作失败,请重试!");
          }
        }
      });
    }


  });

分页查询

后端

后端的话就是使用查询条数limit和偏移量offset来查询数据库,后端得根据前端传来的页码pageIndex和每页最大查询数maxSize来计算出偏移量:

当pageIndex是1的时候,分析可得此时的偏移量offset应该为0,这次查询时查出表里最开头的maxSize条数据;
当pageIndex是2的时候,分析可得此时的偏移量offset是在pageIndex=1查询了maxSize条数据之后的位置,也就是从offset=maxSize处往后在查maxSize条;
当pageIndex是3的时候,offset就得是从maxSize*2处完后查了
所以可以得offset是和maxSize有关系的
使用下面这个公式就可以计算出偏移量

int offset = (pageIndex-1)*maxSize;

后端查询出每页的数据之后,在计算一下每页查maxSize最多可以查多少页maxPage返回给前端

  //先查询出数据库里面全部的文章数
        int total = articleService.getCount();
        //根据每页的最大展示数,计算出最大页数,没有整除就进一
        double maxPage = total/(maxSize*1.0);
        //向上取整
        int finalMaxPage = (int)Math.ceil(maxPage);

后端将每页的maxSize文章数和最大页面数maxPage通过map结构返回给前端:

 @RequestMapping("/getartlist")
    public AjaxResult paging(Integer pageIndex,Integer maxSize){
        //0.非空校验
        if (pageIndex == null || maxSize== null){
            return AjaxResult.fail(-1,"非法参数");
        }
        //根据pageIndex和maxSize计算出偏移量offset
        int offset = (pageIndex-1)*maxSize;
        //根据maxSize和offset可以查询出当前页面的数据
        List<Article> list = articleService.paging(maxSize,offset);
        /**
         * 尾页处理
         */
        //先查询出数据库里面全部的文章数
        int total = articleService.getCount();
        //根据每页的最大展示数,计算出最大页数,没有整除就进一
        double maxPage = total/(maxSize*1.0);
        //向上取整
        int finalMaxPage = (int)Math.ceil(maxPage);
        //使用map键值对,将每页的文章和最大页数返回给前端
        Map<Object,Object> result = new HashMap<>();
        result.put("list",list);
        result.put("finalMaxPage",finalMaxPage);
        return AjaxResult.success(200,result);


    }

前端

前端定义三个变量:

 //当前页的页码,以querystring的形式拼到url里面
    var pageIndex =1;
    //每页展示的数据条数
    var maxSize = 2;
    //最大页数
    var maxPage = 1;

页码pageInex在点击下一页和上一页按钮时拼接到url里上
在这里插入图片描述

initpage()函数根据pageIndex和maxSize直接给服务器发送ajax请求,前端接收每页文章数maxSize和最大页码数maxPage

 //当前页的页码,以querystring的形式拼到url里面
    var pageIndex =1;
    //每页展示的数据条数
    var maxSize = 2;
    //最大页数
    var maxPage = 1;
    //得到当前url中的pageIndex
        pageIndex = getUrlValue("pageIndex")==""?1:getUrlValue("pageIndex");
function initpage() {
          //发送一个ajax请求,获取数据库的全部文章
        jQuery.ajax({
        url: "/art/getartlist",
        type: "Post",
        data:{"pageIndex": pageIndex,"maxSize": maxSize},
        success: function (result){
            //对返回结果进行数据校验
            if(result != null && result.code == 200 && result.data.list.length>0){
                //这个变量用来拼接html标签
                var artListHTML ="";
                for (var i = 0; i<result.data.list.length; i++){
                    //每一篇文章都在list里面
                    var articleinfo = result.data.list[i];
                    //拼标签
                    artListHTML += "<div class='blog'>";
                    artListHTML += "<div class=\"title\">"+articleinfo.title+"</div>";
                    artListHTML += "<div class=\"date\">"+articleinfo.updatetime+"</div>";
                    artListHTML += " <div class=\"desc\">\n" + articleinfo.content+"</div>";
                    artListHTML += " <a href=\"blog_content.html?id=" + articleinfo.id
                        + "\" class=\"detail\">查看全文 &gt;&gt;</a>";
                    artListHTML +=  "</div>"
                }
                //将artListHTML标签添加到父标签 <div class="blog" id="listblog">里面
                jQuery("#listblog").html(artListHTML);


            }
            //获取最大页数
            maxPage = result.data.finalMaxPage;

        }

    })
    }
    initpage();

首页的url里面没有pageIndex 在这里插入图片描述

对于首页的处理
在这里插入图片描述

评论区

后端

评论区分有两点关键实现:
一、多级评论,一级和二级评论
二、回复评论
服务器这边的实现:创建一张表,表名为comments,字段看下面的图片。
在这里插入图片描述
其中我用toUserName和theRootId这个字段来控制一级评论和二级评论,一条一级评论的特点是没有toUserName为nul,theRootId也为null,前端标签里只需要获取到该条评论的userName、commentTime和commentText然后打印出来就行;二级评论需要知道toUserName该条评论回复谁,theRootId该条评论是哪条一级评论下的二级评论。
在这里插入图片描述
在数据库里查询所有评论时:
1、先将theRootId=null的所有一级评论查询出来放到一张链表里theRootList;
2、然后遍历这个链表,获取每个一级评论的commentId,然后使用where theRootId=commentId(一级评论的id)将所有的二级评论查询出来放到一个链表里list,然后使用头插法将一级评论插入到二级评论里;
3、list链表就包含了一级评论和一级评论下的所有二级评论,在将list链表添加到comments链表里面,comments是链表嵌套链表的结构,comments表示每篇博客的所有评论,comments每一个元素list就表示一条一级评论和所有的二级评论;

 @Test
    void getUnderTheRootCommentsByBlogId() {
        //用来装一篇博客的所有评论
       LinkedList<LinkedList<Commet>> comments = new LinkedList<>();
        //获得所有的一级评论
       LinkedList<Commet> theRootList =  commentMapper.getTheRootCommentsByBlogId();
        for (int i = 0; i < theRootList.size(); i++) {
            //获得一级评论的commentId
            int theRootId = theRootList.get(i).getCommentId();
            //获得所有一级评论下的二级评论
           LinkedList<Commet> list =  commentMapper.getUnderTheRootCommentsByBlogId(theRootId);
            /**
             * 二级评论和一级评论合并
             */
            //将一级评论放在头插在的第一个位置,二级评list为空,直接插入一级评论就不为空了
            Commet commet = theRootList.get(i);
            list.addFirst(commet);
            comments.add(list);
        }
        System.out.println(comments);


    }

在这里插入图片描述

前端

前端的实现,思路:
在这里插入图片描述

md5加盐加密

MD5是一种信息摘要算法,是一种单向的哈希函数,将一串字符经过一系列的按位与,按位异或等运算生成固定长度128比特的散列值,这些运算的过程就造成了信息的缺失,因此不可逆。
如果直接只对明文进行md5运算得到散列值的话是不安全,任何一个密码串都对应唯一一个md5的值(工程上来说),如果密码长度是固定的,那么可以搞一个表.将密码所有组合的MD5的值全部列出,然后拿着表里的md5的值去和某个密码的MD5的值去比较,如果相等在就能找到MD5匹配的明文密码,这就是暴力破解的过程.有个彩虹表就可以做到这样的暴力破解。
加长密码的长度,可以有效的提高暴力破解的难度代价就越高,密码越长,彩虹表的量级就非常大,想象65位长度的密码的所有组合有多少种。
因此采用明文+盐方式将密码变长!!!
加密过程:
使用UUID.randomUUID()生成的唯一的随机值当做盐值salt,用明文和盐值salt拼接然后生成MD5散列值md5Password,然后拼接ciphertext = salt+$+md5Password存储到数据库里,如果你想暴力破解ciphertext那就代价很高了,这是65位的字符串,你单单破解唯一的32位salt你都得付出极大的代价.
解密过程:
由于md5不可逆,所以解密只能是拿着明文和盐值在进行一次相同加密操作,来看最后得到的ciphertext是否和数据库里存储的ciphertext一样,如果一样就说明密码正确,如果不一样就说明密码错误.

加密过程:

 /**
     * 对明文进行加密,产生盐值
     * @param password
     * @return存储到数据库里面
     */
    public static String encrypt(String password){
        //1.产生盐值
        String salt = UUID.randomUUID().toString().replace("-","");
        //2.明文和盐值进行md5加密
        String md5Password = DigestUtils.md5DigestAsHex((password+salt).getBytes());
        //3.拼接:盐值+$+密文
        String ciphertext = salt+"$"+md5Password;
        return ciphertext;
    }

解密过程:
先获取到数据库里的ciphertext,然后把$前面的盐值拿出来进和密码进行加密

  /**
     *
     * @param inputPassword 用户输入的密码
     * @param finalPassword 数据库保存的最终密码
     * @return
     */
    public static Boolean check(String inputPassword,String finalPassword){
    //0.非空校验
        if(!StringUtils.hasLength(inputPassword) || !StringUtils.hasLength(finalPassword) || finalPassword.length()!=65){
            return false;
        }
        //1.从数据库的密码里面获取盐值
        String salt = finalPassword.split("\\$")[0];
        //2.输入的明文密码和盐值进行md5加密
        String ciphertext = encrypt(inputPassword,salt);
        //3.判断是否相等
        if(ciphertext.equals(finalPassword)){
            return true;
        }
        return false;


    }
    
    /**
     *不产生盐值, 用与校验密码
     * @param password 前端传过来
     * @param salt 从数据库里获取
     * @return
     */
    public static String encrypt(String password,String salt){
        //0.非空检验
        if(!StringUtils.hasLength(password) || !StringUtils.hasLength(salt)){
            return null;
        }
        //1.加密
        String md5Password = DigestUtils.md5DigestAsHex((password+salt).getBytes());
        //2.盐值+$+密文 32位盐值+1位$+32位md5的值 =65位
        String ciphertext = salt+"$"+md5Password;
        return ciphertext;

    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/991091.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MojoTween:使用「Burst、Jobs、Collections、Mathematics」优化实现的Unity顶级「Tween动画引擎」

MojoTween是一个令人惊叹的Tween动画引擎&#xff0c;针对C#和Unity进行了高度优化&#xff0c;使用了Burst、Jobs、Collections、Mathematics等新技术编码。 MojoTween提供了一套完整的解决方案&#xff0c;将Tween动画应用于Unity Objects的各个方面&#xff0c;并可以通过E…

恒运资本:人民币汇率何时走出低谷?

9月7日&#xff0c;国家外汇管理局发布统计数据显现&#xff0c;到2023年8月末&#xff0c;我国外汇储藏规划为31601亿美元&#xff0c;较7月末下降442亿美元&#xff0c;降幅为1.38%。 国家外汇管理局相关负责人表明&#xff0c;2023年8月&#xff0c;受首要经济体微观经济数…

《机器人学一(Robotics(1))》_台大林沛群 第 7 周 【轨迹规划_综合】Quiz 7

题 4-5 存疑&#xff0c;仅供参考&#xff0c;欢迎交流 文章目录 题4-9&#xff1a;题4-5求解代码&#xff1a; Python题6-7求解代码&#xff1a; Python求解 θ4-θ6 时&#xff0c; 记得 将 R 改成相应的&#xff01;&#xff01;&#xff01;&#xff01; 题8-9求解代码&…

深浅拷贝与赋值

数据类型 数据类型 在JavaScript中&#xff0c;数据类型有两大类。一类是基本数据类型&#xff0c;一类是引用数据类型。 基本数据类型有六种&#xff1a;number、string、boolean、null、undefined、symbol。 基本数据类型存放在栈中。存放在栈中的数据具有数据大小确定&a…

2023高教社杯数学建模B题思路代码 - 多波束测线问题

# 1 赛题 B 题 多波束测线问题 单波束测深是利用声波在水中的传播特性来测量水体深度的技术。声波在均匀介质中作匀 速直线传播&#xff0c; 在不同界面上产生反射&#xff0c; 利用这一原理&#xff0c;从测量船换能器垂直向海底发射声波信 号&#xff0c;并记录从声波发射到…

更换Eclipse的JDK版本

点击window->Preferences 选择Installed JREs 点击 Add 按钮&#xff0c; 选择Standard VM, 点击 Next。然后选择自己安装的JDK路径

测量仪器方案——核辐射检测仪方案

核辐射在我们日常生活中是比较常见的&#xff0c;基本在任何地方都会存在或多或少的辐射放射源&#xff0c;当它的强度超过一定数值后&#xff0c;就会对人体造成一定的影响。如果是在辐射强度过高的领域工作时&#xff0c;建议选择核辐射检测仪作为防护仪器。目前核辐射检测仪…

一点感受

做了两天企业数字化转型的评委&#xff0c;涉及全国最顶级的公司、最顶级的实际落地项目案例&#xff0c;由企业真实的落地团队亲自当面讲解。主要是为了了解了解真实的一线、真实的客户、真实的应用现状和应用水平。 &#xff08;1&#xff09;现状 我评审的涉及底层技术平台&…

无涯教程-JavaScript - HEX2DEC函数

描述 HEX2DEC函数将十六进制数字转换为十进制。 语法 HEX2DEC (number)争论 Argument描述Required/Optionalnumber 您要转换的十六进制数。 数字不能超过10个字符(40位)。数字的最高有效位是符号位。其余的39位是幅度位。 负数使用二进制补码表示。 Required Notes 十六进…

在Spring Boot项目中使用JPA

1.集成Spring Data JPA Spring Boot提供了启动器spring-boot-starter-data-jpa&#xff0c;只需要添加启动器&#xff08;Starters&#xff09;就能实现在项目中使用JPA。下面一步一步演示集成Spring Data JPA所需的配置。 步骤01 添加JPA依赖。 首先创建新的Spring Boot项目…

Git_回退到上一次commit与pull

git 回退到上个版本 rollback 回滚 git reset HEAD&#xff0c; git 回退到上一版本

Codeforces Round 895 (Div. 3) A ~ F

Dashboard - Codeforces Round 895 (Div. 3) - Codeforces A 问多少次能使a 和 b相等&#xff0c;就是abs(a - b) / 2除c向上取整&#xff0c;也就是abs(a - b)除2c向上取整。 #include<bits/stdc.h> #define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); #de…

vue checkbox-group和checkbox动态生成,问题解决

源码 <el-checkbox-group v-model"form[keyItem.name]"><el-checkboxv-for"(checkboxItem,cindex) in keyItem.options.split(,)":key"cindex":label"checkboxItem"></el-checkbox></el-checkbox-group> 我是…

freertos之任务运行时间统计实验

这里写目录标题 任务时间统计函数时间统计API函数使用流程实验 任务时间统计函数 void vTaskGetRunTimeStats(char * pcWriteBuffer); 时间统计API函数使用流程 实验 1.首先现在FreeRTOSConfig.h文件里将configGENERATE_RUN_TIME_STATS 和configUSE_STATS_FORMATTING_FUNCTIO…

scanf和scanf_s函数详解

目录 引言&#xff1a; 1.scanf函数的用法&#xff1a; 2.scanf_s函数的用法&#xff1a; 3.scanf和scanf_s的区别&#xff1a; 结论&#xff1a; 引言&#xff1a; 在C语言中&#xff0c;输入函数scanf是非常常用的函数之一&#xff0c;它可以从标准输入流中读取数据并将其…

在学习编程的过程中,我会记录下以下内容:

在学习编程的过程中&#xff0c;我会记录下以下内容&#xff1a; 常用代码片段&#xff1a;我会记录一些常用的代码片段&#xff0c;例如文件读写、列表操作、字符串处理等。这些代码片段可以在日常编程中快速复用&#xff0c;提高编码效率。 # 文件读取 with open(file.txt,…

手术麻醉信息系统源码 医院麻醉监护的功能覆盖整个手术与麻醉的全过程

手术麻醉信息系统源码 PHP手麻系统源码 手术麻醉信息系统是HIS产品的中的一个组成部分&#xff0c;主要应用于医院的麻醉科&#xff0c;属于电子病历类产品。医院麻醉监护的功能覆盖整个手术与麻醉的全过程&#xff0c;包括手术申请与排班、审批、安排、术前、术中和术后。 手…

0908集合总结

Java集合 Java的集合类主要由Collection接口和Map接口派生而来&#xff0c;其中Collection接口由两个常用的子接口&#xff0c;即List接口和Set接口&#xff0c;所以常说的Java集合框架由三大类接口构成&#xff08;Map接口、List接口和Set接口&#xff09; List接口 List的…

无锡哲讯与喜德金属联手推动“百城千园行”“十园千企”无锡站活动,数字化赋能活动动

当前&#xff0c;新一轮科技革命和产业革命席卷全球&#xff0c;数字经济发展速度之快、辐射之广、影响之深前所未有。2023年9月7日&#xff0c;为加快推进制造业智能化改造、数字化转型&#xff0c;促进供需两端精准对接&#xff0c;加速提升汽车及零部件产业集群企业智能制造…

【多线程】内存可见性

一、什么是内存可见性 内存可见性是在编译器优化的背景下&#xff0c;一个线程修改了变量而另一个线程却没有感知到修改。举个例子&#xff0c;一个线程一直频繁的读取变量n并将n值与某一值进行比较&#xff0c;在底层这个操作对应着两个指令&#xff1a;读取内存中的n值加载到…