博客系统项目

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.titlevar 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计算出偏移量offsetint 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中的pageIndexpageIndex = 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就表示一条一级评论和所有的二级评论;

 @Testvoid getUnderTheRootCommentsByBlogId() {//用来装一篇博客的所有评论LinkedList<LinkedList<Commet>> comments = new LinkedList<>();//获得所有的一级评论LinkedList<Commet> theRootList =  commentMapper.getTheRootCommentsByBlogId();for (int i = 0; i < theRootList.size(); i++) {//获得一级评论的commentIdint 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.rhkb.cn/news/129638.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

“JSR303和拦截器在Java Web开发中的应用与实践“

目录 引言JSR303什么是JSR303?为什么要使用JSR303?常用注解快速入门JSR303 拦截器什么是拦截器拦截器与过滤器应用场景快速入门拦截器 总结 引言 在Java Web开发过程中&#xff0c;我们经常会遇到需要对输入数据进行验证和处理&#xff0c;同时需要对请求进行拦截与控制的需…

通过idea实现springboot集成mybatys

概述 使用springboot 集成 mybatys后&#xff0c;通过http请求接口&#xff0c;使得通过http请求可以直接直接操作数据库&#xff1b; 完成后端功能框架&#xff1b;前端是准备上小程序&#xff0c;调用https的请求接口用。简单实现后端框架&#xff1b; 详细 springboot 集…

Scrum工作模式的角色和活动

​Scrum工作模式是一种敏捷软件开发方法&#xff0c;其核心是团队合作和自我组织&#xff0c;旨在通过短周期的迭代开发&#xff0c;实现快速反馈和持续改进。 Scrum工作模式包括以下角色和活动&#xff1a; 1、产品负责人&#xff08;Product Owner&#xff09;&#xff1a;…

laravel系列(二) Dcat admin框架开发工具使用

开发工具可以非常好的帮助我们去快速的开发CURD等操作,但也是有部分框架有些不是太便捷操作,这篇博客主要为大家介绍Dcat admin的开发工具详细使用. 如何创建页面: 在联表我们首先要去.env文件中去找连接数据库方法: APP_NAMELaravel APP_ENVlocal APP_KEYbase64:thO0lOVlzj0…

Allegro166版本如何在颜色管理器中实时显示层面操作指导

Allegro166版本如何在颜色管理器中实时显示层面操作指导 在用Allegro166进行PCB设计的时候,需要在颜色管理器中频繁的开关层面。但是166不像172一样在颜色管理器中可以实时的开关层面,如下图 需要打开Board Geometry/Soldermask_top层,首先需要勾选这个层面,再点击Apply即…

论文简读 LORA: LOW-RANK ADAPTATION OF LARGE LANGUAGE MODELS

论文地址&#xff1a;https://arxiv.org/pdf/2106.09685.pdf 项目地址&#xff1a;https://github.com/microsoft/LoRA 全文翻译地址&#xff1a;https://zhuanlan.zhihu.com/p/611557340 本来想自行翻译的&#xff0c;但最近没有空 1、关键凝练 1.1 LORA是什么&#xff1f; …

基于java SpringBoot和Vue uniapp的影楼摄影预约小程序

摘要 今天信息技术的发展很快&#xff0c;其足迹在我们的生活中随处可见。它影响着我们的衣食住行等各种需求。影响也在逐渐增加&#xff0c;逐渐渗透到各行各业&#xff0c;在这种背景下&#xff0c;经过实地考察后&#xff0c;为了让婚纱照管理更加高效方便&#xff0c;我决定…

gitlab 点击Integrations出现500错误

背景&#xff1a;在新服务器重新搭建了gitlab&#xff0c;并导入原来gitlab的备份&#xff0c;在项目中点击点击Integrations出现500错误。 解决方法&#xff1a;1.进入新服务器&#xff0c;将 /etc/gitlab/gitlab-secrets.json重命名为 /etc/gitlab/gitlab-secrets.json.bak …

JavaScript的内置类

一、认识包装类型 1.原始类型的包装类 JavaScript的原始类型并非对象类型&#xff0c;所以从理论上来说&#xff0c;它们是没有办法获取属性或者调用方法的。 但是&#xff0c;在开发中会看到&#xff0c;我们会经常这样操作&#xff1a; var message "hello world&q…

文件上传漏洞(CVE-2022-30887)

简介 多语言药房管理系统&#xff08;MPMS&#xff09;是用PHP和MySQL开发的&#xff0c;该软件的主要目的是在药房和客户之间提供一套接口&#xff0c;客户是该软件的主要用户。该软件有助于为药房业务创建一个综合数据库&#xff0c;并根据到期、产品等各种参数提供各种报告…

python超详细安装

目录 初始python获取python安装包python解释器安装pycharm编译器安装pycharm的简单使用&#xff08;第一个hello world&#xff09; 初始python Python 是一款易于学习且功能强大的编程语言。 它具有高效率的数据结构&#xff0c;能够简单又有效地实现面向对象编程。 Python简…

uni-app(微信小程序)图片旋转放缩,文字绘制、海报绘制

总结一下&#xff1a; 要进行海报绘制离不开canvas&#xff0c;我们是先进行图片&#xff0c;文字的拖拽、旋转等操作 最后再对canvas进行绘制&#xff0c;完成海报绘制。 背景区域设置为 position: relative&#xff0c;方便图片在当前区域中拖动等处理。添加图片&#xff0…

Python 图形化界面基础篇:添加标签( Label )到 Tkinter 窗口

Python 图形化界面基础篇&#xff1a;添加标签&#xff08; Label &#xff09;到 Tkinter 窗口 引言什么是 Tkinter 标签&#xff08; Label &#xff09;&#xff1f;步骤1&#xff1a;导入 Tkinter 模块步骤2&#xff1a;创建 Tkinter 窗口步骤3&#xff1a;创建标签&#x…

Mybatis---resultMap详解

目录 一、resultMap介绍 二、自定义映射关系 一、resultMap介绍 该标签的作用是自定义映射关系。 Mybatis可以将数据库结果封装到对象中&#xff0c;是因为结果集和对象属性名相同&#xff08;也就是你写的pojo类型的参数名和数据库的字段名相同&#xff09; 但是如果当他们不…

npm publish包报404,is not in the npm registry错误

1. 指定发布目标2. 登录npm&#xff0c;使用登录名发布包&#xff0c;包名命名原则“登录名/包名”&#xff0c;或 “包名” 3. 删除某一个版本npm unpublish pvfhv/eslint-config-prettier1.0.1 --force 删除后的版本不能重复使用&#xff0c;正式解释&#xff1a; Unfortun…

小米13Pro/13Ultra刷面具ROOT后激活LSPosed框架微X模块详细教程

喜欢买小米手机&#xff0c;很多是因为小米手机的开放&#xff0c;支持root权限&#xff0c;而ROOT对普通用户来说更多的是刷入DIY模块功能&#xff0c;今天ROM乐园小编就教大家如何使用面具ROOT&#xff0c;实现大家日常情况下非常依赖的微X模块功能&#xff0c;体验微X模块的…

makefile之链接静态库

make之链接静态库 (1)方法一: 指定静态库全路径和全名 APP_S_LIBS ./app_lib/libhost.a $(CC) $(CFLAGS) $(SRCOBJ) $(APP_S_LIBS) -o $(TARGET) APP_HEAD_DIR -I./include #APP_LIBS_DIR -L ./app_lib#APP_S_LIBS -lhost APP_S_LIBS ./app_lib/libhost.aCFLAGS $(APP_…

企业密码安全:ADSelfService Plus 提升密码管理的千里之行

在当今数字化时代&#xff0c;企业的密码安全变得至关重要。密码是保护企业敏感信息和数据的第一道防线&#xff0c;而有效的密码管理对于确保网络安全至关重要。ADSelfService Plus是一款强大的密码管理和自助服务解决方案&#xff0c;它在提供密码安全方面走在了前沿。 ADSel…

Ajax + Promise复习简单小结simple

axios使用 先看看老朋友 axios axios是基于Ajaxpromise封装的 看一下他的简单使用 安装&#xff1a;npm install axios --save 引入&#xff1a;import axios from axios GitHub地址 基本使用 axios({url: http://hmajax.itheima.net/api/province}).then(function (result…

普中 51 单片机点亮LED灯

普中 51 单片机 &#xff08;STC89C52RC&#xff09; LED / IO 将LED1进行闪烁操作 为啥要进行延时操作&#xff1f;依据人的肉眼余晖效应&#xff0c; 延时时间不能太短&#xff0c;否则就无法观察到 LED 闪烁 #include "reg52.h" typedef unsigned int u16; //对…