目录
项目简介
哈喽,经过了九九八十一难,终于到了项目的部分,这是我做的第四版博客系统的项目,这个项目前两版是Servlet版本的,第三版是SSM版本的,这个版本是在第三版的升级版,主要升级方面如下:
- 实现了自定义头像功能
- 实现了验证码功能
- 对用户的草稿进行了管理
- 进行了分页的设计
- 对用户的密码进行了加密处理
- 新增了评论和点赞功能
那么下面我们就从注册页面来开始说起吧
项目源码:https://gitee.com/chen-xuanyou/java-ee/tree/master/xyblog
1.注册页
注册页面就是很简单的输入几个相关的信息,其中最难的就是图片的保存。首先我们需要将用户输入的图片进行保存,保存到本地的磁盘中,然后在数据库中存储图片的路径。在前端我采用的是form表单配合ajax进行传输数据,因为这样就可以将文件和数据打包成一个整体进行传输。
这样我们后端就可以接收到带文件的数据了。我们在后端可以使用一个MultipartFile的文件类型来接收文件,然后重新给文件命名,然后将文件保存在本地。
重新命名我才用的是UUID来设置文件的名字,同时去除字符串的‘-’,同时这个方法在后面的密码加密中也会用到。
然后我们将相关数据存入数据库即可。
2.登陆页
注册成功之后就来到了登陆页面,首先我们先看看登陆页面的样子吧。
这个页面中用户输入自己的账号和密码,然后 输入验证码,首先我们需要在前端判断是否输入了以下的三个数据,然后再调用ajax。
现在最主要的问题就是如何设置验证码的图片,以及如和设置验证码。首先在此之前我们需要了解验证码的工作机制。
我们需要在后端先生成一串随机字符然后存入session,然后再根据用户输入的字符串和存在session中的字符串做比对,然后就可以实现验证码的校验了;那么接下来就是如何让验证码以图片的方式展现,这里我们使用到了canvas这个技术,通过canvas将数字展现为图片,然后再将图片展现出来。
@RequestMapping("/login")
public Object login(String username, String password, String identifyCode, HttpServletRequest request){
//从session中取出验证码
HttpSession session=request.getSession(false);
String sessionCode = (String)session.getAttribute("identifyCode");
System.out.println(username);
System.out.println(password);
if (identifyCode.equalsIgnoreCase(sessionCode)){
Userinfo chk=userService.loginChk(username);
if(SecuryUtil.decryption(password,chk.getPassword())){
//登陆成功,存一手session
session.setAttribute(ConstantVariable.SESSION_KEY_VALUE,chk);
return AjaxRespons.success(1);
}else {
return AjaxRespons.fail(200,-1,"请输入正确的账号或密码");
}
}else{
System.out.println("验证码错误");
return AjaxRespons.fail(200,-1,"验证码错误");
}
}
/**
* 给前端返回一个验证码图片
* @return
*/
@RequestMapping("/identifyImage")
public void identifyImage(HttpServletResponse response, HttpServletRequest request){
//创建随机验证码
IdentyUtil utils = new IdentyUtil();
String identifyCode = utils.getIdentifyCode();
//session存入验证码
HttpSession session= request.getSession(true);
session.setAttribute("identifyCode", identifyCode);
//根据验证码创建图片
BufferedImage identifyImage = utils.getIdentifyImage(identifyCode);
//回传给前端
utils.responseIdentifyImg(identifyImage,response);
}
绘制验证码图片
package com.example.xyblog.Common;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
public class IdentyUtil {
//设置图片宽
private int width = 95;
//设置图片高度
private int height = 25;
//设置干扰线数量
private int lineSize = 40;
//随机产生数字和字母组合的字符串
private String randString = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private Random random = new Random();
/**
* 获得字体
*/
private Font getFont() {
return new Font("Fixedsys", Font.CENTER_BASELINE, 18);
}
/**
* 获得颜色
*/
private Color getRandColor(int fc, int bc) {
if (fc > 255) {
fc = 255;
}
if (bc > 255) {
bc = 255;
}
int r = fc + random.nextInt(bc - fc - 16);
int g = fc + random.nextInt(bc - fc - 14);
int b = fc + random.nextInt(bc - fc - 18);
return new Color(r, g, b);
}
/**
* 获取验证码
*
* @return
*/
public String getIdentifyCode() {
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < 4; i++) {
char c = randString.charAt(random.nextInt(randString.length()));
buffer.append(c);
}
return buffer.toString();
}
/**
* 生成随机图片
*
* @param identifyCode
* @return
*/
public BufferedImage getIdentifyImage(String identifyCode) {
//BufferedImage类是具有缓冲区的Image类,Image类是用来描述图像信息的类
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
//产生Image对象的Graphics对象,改对象可以在图像上进行各种绘制操作
Graphics graphics = image.getGraphics();
//图片大小
graphics.fillRect(0, 0, width, height);
//字体大小
graphics.setFont(new Font("Times New Roman", Font.ROMAN_BASELINE, 18));
//字体颜色
graphics.setColor(getRandColor(110, 133));
//绘制干扰线
for (int i = 0; i <= lineSize; i++) {
drawLine(graphics);
}
//绘制随机字符
drawString(graphics, identifyCode);
graphics.dispose();
return image;
}
/**
* 绘制字符串
*/
private void drawString(Graphics g, String identifyCode) {
for (int i = 0; i < identifyCode.length(); i++) {
g.setFont(getFont());
g.setColor(new Color(random.nextInt(101), random.nextInt(111), random
.nextInt(121)));
g.translate(random.nextInt(3), random.nextInt(3));
g.drawString(String.valueOf(identifyCode.charAt(i)), 13 * i + 20, 18);
}
}
/**
* 响应验证码图片
*
* @param identifyImg
* @param response
*/
public void responseIdentifyImg(BufferedImage identifyImg, HttpServletResponse response) {
//设置响应类型,告诉浏览器输出的内容是图片
response.setContentType("image/jpeg");
//设置响应头信息,告诉浏览器不用缓冲此内容
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expire", 0);
try {
//把内存中的图片通过流动形式输出到客户端
ImageIO.write(identifyImg, "JPEG", response.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 绘制干扰线
*/
private void drawLine(Graphics graphics) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(13);
int yl = random.nextInt(15);
graphics.drawLine(x, y, x + xl, y + yl);
}
}
@RequestMapping("/identifyImage")
public void identifyImage(HttpServletResponse response, HttpServletRequest request){
//创建随机验证码
IdentyUtil utils = new IdentyUtil();
String identifyCode = utils.getIdentifyCode();
//session存入验证码
HttpSession session= request.getSession(true);
session.setAttribute("identifyCode", identifyCode);
//根据验证码创建图片
BufferedImage identifyImage = utils.getIdentifyImage(identifyCode);
//回传给前端
utils.responseIdentifyImg(identifyImage,response);
}
浏览器自动识别传来的字节流,然后将字节流转化为图片,这时就有相关的图片生成啦,当然,我们还需要完成一个点击验证码换图片的操作,这个就需要我们的前端来配合操作。
js代码如下:
$("#identify-img").on('click',function (){
// 点击验证码那个图片的时候,我们输入的验证码那个框就会清空
$('#identify-input').val('')
//而且我们点击验证码的时候,希望它可以改变验证码内容,其实是通过发送新请求来改变验证码内容
$('#identify-img').attr('src','/user/identifyImage?'+Math.random());
html:
<div class="row">
<span>验证码:</span>
<input id="identify-input" type="text" name="identifyCode">
<img id="identify-img" src="/user/identifyImage"><br><br>
</div>
当然这里的存session功能和我们以往使用的session不太一样,在这里我们在保存验证码的时候就已经创建了一次session了,所以我们在保存用户的账号和密码时就不要将session的值设置成true。
3.个人列表页
页面展示:
中间的区域用来展示自己的博客,左侧为个人信息栏,我们先从左侧开始讲起。
3.1 个人信息
个人信息主要展示的就是当前登陆用户的头像、姓名、gitee地址和文章数。
3.1.1个人头像
头像的展示当初困扰了我很久,我想过无数种办法,但直到我知道浏览器可以自动解析后端传的流文件的时候我豁然开朗,首先我们需要在前端有一个img标,但是这里的src可不是放图片路径,而是后端对应的接口路径。
后端接口
@RequestMapping("/photoUpload")
public void uploadPhoto(HttpServletResponse response,HttpServletRequest request) throws IOException {
PhotoUtil util=new PhotoUtil();
HttpSession session=request.getSession(false);
Userinfo userinfo= (Userinfo) session.getAttribute(ConstantVariable.SESSION_KEY_VALUE);
String imgPath=userinfo.getPhoto();
Image image = ImageIO.read(new FileInputStream(imgPath));
util.responseImage(image,response);
}
下面我们从源码来看看。
public class PhotoUtil {
public void responseImage(Image image, HttpServletResponse response){
//设置响应类型,告诉浏览器输出的内容是图片
response.setContentType("image/jpeg");
//设置响应头信息,告诉浏览器不用缓冲此内容
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expire", 0);
try {
//把内存中的图片通过流动形式输出到客户端
ImageIO.write((RenderedImage) image, "JPG", response.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
}
从这里设置我们的响应格式,具体为什么这么设置我也不知道哈哈哈哈。
3.1.2用户名&文章数
现在我们根据用户的用户名来修改当前页面的标签内容。我们需要根据当前登陆用户的session来获取当前用户的信息。
Userinfo userinfo=(Userinfo) session.getAttribute(ConstantVariable.SESSION_KEY_VALUE);
这里我们的ConstantVariable.SESSION_KEY是一个常量,那么就会有个问题,程序如何判断我取的到底是谁?其实存储的时候我们同样存储了sessionID,就可以通过这个找到对应的用户信息了。
public class ConstantVariable {
public static final String SESSION_KEY_VALUE = "userinfo_session_value";
}
那么在返回之前我们还要修改当前用户的文章数,这个就需要使用数据库提供的函数来进行。
<select id="countBlogs" resultType="com.example.xyblog.Model.Articleinfo">
select * from articleinfo where uid = #{uid};
</select>
返回一个数组,然后计算数组中的元素就可以获得当前登陆用户的文章数啦。
@RequestMapping("/getUserinfo")
public Object getUserinfo(HttpServletRequest request){
HttpSession session= request.getSession(false);
Userinfo userinfo=(Userinfo) session.getAttribute(ConstantVariable.SESSION_KEY_VALUE);
userinfo.setBcount(userService.countBlogs(userinfo.getId()).size());
return userinfo;
}
3.2文章列表
这里的文章列表只是一个初模型,之后我们完成草稿功能之后再来进行修改。当前的文章列表我们我们会将所有当前用户的文章展示出来,并且提供修改文章和查看文章的按钮。
首先在前端我们需要使用js来进行拼接,所以我们先向后端发送ajax请求,然后触发回调函数,从而完成拼接。
在后端我们需要通过session拿到当前登陆用户的信息,然后查询当前登陆用户的所有文章。
在前端我们还需要一个函数来获取文章的简介,所以我们可以自由的设置一个简介的最大长度,然后使用字符串的相关函数来截取。
js:
var descLength = 80; // 简介最大长度
// 字符串截取,将文章正文截取成简介
function mySubstr(content){
if(content.length>descLength){
return content.substr(0,descLength);
}
return content;
}
function getList(){
$.ajax({
url:"/art/getMyList",
method:"POST",
data:{
},
success:function (result) {
if (result.code == 200 && result.data != null) {
//开始拼接
let content = result.data;
console.log(content);
let container = document.querySelector('.container-right');
for (let blog of content) {
let blogDiv = document.createElement('div');
blogDiv.className = 'blog';
if(blog.state==0){
let titleDiv = document.createElement('div');
titleDiv.className = 'title';
titleDiv.innerHTML = blog.title+"(草稿)";
blogDiv.appendChild(titleDiv);
}else{
// 创建博客标题
let titleDiv = document.createElement('div');
titleDiv.className = 'title';
titleDiv.innerHTML = blog.title;
blogDiv.appendChild(titleDiv);
}
// 创建日期
let dateDiv = document.createElement('div');//外层
dateDiv.className = 'date';
//点赞数
let thumbSpan = document.createElement('span');
thumbSpan.className = 'thumb';
thumbSpan.innerHTML = "点赞数:"+blog.thumb+" ";
//创建时间
let dateSpan = document.createElement('span');
dateSpan.className = 'data';
dateSpan.innerHTML = blog.updatetime;
//阅读量
let rcountSpan = document.createElement('span');
rcountSpan.className = 'rcount';
rcountSpan.innerHTML = "阅读量:"+blog.rcount+" ";
//拼接
dateDiv.appendChild(dateSpan);
dateDiv.appendChild(thumbSpan);
dateDiv.appendChild(rcountSpan);
blogDiv.appendChild(dateDiv);
// 创建摘要
let descDiv = document.createElement('div');
descDiv.className = 'desc';
descDiv.innerHTML = mySubstr(blog.content);
blogDiv.appendChild(descDiv);
// 创建查看全文按钮
let a = document.createElement('a');
let a2 = document.createElement('a');
a.className = 'detail';
a2.className = 'update';
a.innerHTML = '查看全文 >>';
a2.innerHTML = '修改文章 >>';
a.href = 'blog_content.html?id=' + blog.id;
a2.href = 'blog_update.html?id=' + blog.id;
blogDiv.appendChild(a);
blogDiv.appendChild(a2);
// 把 blogDiv 加入外层元素
container.appendChild(blogDiv);
}
}
else
{
jQuery(".container-right").html("<h1>暂无数据</h1>");
}
},
error:function(err) {
if (err != null && err.status == 401) {
alert("用户未登录,即将跳转到登录页!");
// 已经被拦截器拦截了,未登录
location.href = "/login.html";
}
}
});
}
getList()
@RequestMapping("/getMyList")
public Object getMyList( HttpServletRequest request){
HttpSession session= request.getSession(false);
System.out.println(1);
if(session != null && session.getAttribute(ConstantVariable.SESSION_KEY_VALUE) != null){
Userinfo userInfo= (Userinfo) session.getAttribute(ConstantVariable.SESSION_KEY_VALUE);
//从数据库中拿到文章存在List里面
List<Articleinfo> list=articleService.getMyList(userInfo.getId());
return list;
}
return null;
}
4.文章详情页
在个人列表页中点击查看文章,就可以进入文章详情。
我们发现这里其实布局和个人详情页是非常像的,这里我们着重讲右边的部分。首先我们还是需要前端发送ajax请求,然后再进行操作;那么现在问题给到后端,我如何知道现在是在哪篇文章呢,我们可以通过url来传文章id。
所以我们前端在ajax传递参数的时候需要将文章的id带上。所以我们需要写一个函数来获取url中的参数。
function getURLParam(key){
var params = location.search;
if(params.indexOf("?")>=0){
params = params.substring(1);
var paramArr = params.split('&');
for(var i=0;i<paramArr.length;i++){
var namevalues = paramArr[i].split("=");
if(namevalues[0]==key){
return namevalues[1];
}
}
}else{
return "";
}
}
这样我们就可以在后端轻松的获取到文章啦
ArticleController:
@RequestMapping("/getBlog")
public Object getBlog(Integer id){
if(id == null){
return AjaxRespons.fail(-1,"传入数据出错");
}
Articleinfo articleinfo=articleService.getBlog(id);
if(articleinfo == null){
return AjaxRespons.fail(-1,"暂无查询数据");
}
return articleinfo;
}
JS:
function getBlog(){
var param=getURLParam("id");
$.ajax({
url:"/art/getBlog",
method: "POST",
data: {
"id":param
} ,
success:function (result){
if(result.code == 200 && result.data!=null){
let art=result.data;
console.log(art);
let h3 = document.querySelector('.blog-content h3');
h3.innerHTML = art.title;
let divDate = document.querySelector('.blog-content .date');
divDate.innerHTML = art.updatetime;
editormd.markdownToHTML('editorDiv', {
markdown: art.content
});
}else{
alert(result.msg)
}
}
});
}
getBlog();
5.文章操作
5.1文章修改
点击修改文章后进入文章修改界面。
首先我们需要在这里显示出已经写好文章的相关信息,那么同样需要在url中带上我们文章id,然后再在后端进行操作。
var art = result.data;
jQuery("#title").val(art.title);
initEdit(art.content);
当然我们在这里需要多做一步就是检查当前的用户是否拥有所有权。如果是别人就不能修改。
5.2文章保存
修改完成之后就是保存用户的修改操作 ,首先我们还是需要获取标题和文章的内容,然后将这些传给后端,再由后端保存到数据库中。
$.ajax({
url:"/art/update",
method: "POST",
data: {
"title":title.val(),
"content":editor.getValue(),
"aid":aid
},
success: function (result){
if(result.code == 200 && result.data != -1){
alert("修改成功");
location.href="myblog_list.html";
}else {
alert("修改失败,请重试");
}
},
error: function (err){
alert("传入数据出错,请重新传入")
}
})
@RequestMapping("/update")
public Object update(String title,String content,Integer aid){
//非空校验
if(StringUtils.hasLength(title) && StringUtils.hasLength(content) &&
aid != null && aid > 0){
//有标题有正文,接下来操作数据库
int ret=articleService.update(title,content,aid,1);
return ret;
}else {
return -1;
}
}
6.文章列表页分页的操作
6.1分页原理
来到文章列表页,这里将显示所有的文章,以及做分页的操作。那么首先我们先来了解下分页操作是个啥。
红色框里面的就是我们需要的,那么分页的原理又是啥,首先我们需要确认一页中有几个文章(psize),以及当前页数(pindex)是多少,psize是由程序员自己定义的,我这里设置的psize是3大家可以根据自己的情况来设置。了解到这个后,后端是如何从这两个参数中拿到对应的文章呢;在学习MySQL中有一个关键子limit,这个当时我学的时候觉得这玩意没啥用,当我做这个功能的时候突然想了起来,豁然开朗。
分页公式:
limit psize offset (pindex-1)*pagesize
有了这个公式我们就可以完成分页的操作了。
6.2分页功能
知道了分页的原理,接下来就是实现了,首先我们需要给这几个按键设置上onclick方法,
<div class="blog-pagnation-wrapper">
<button class="blog-pagnation-item" onclick="firstPage()">首页</button>
<button class="blog-pagnation-item" onclick="prevPage()">上一页</button>
<button class="blog-pagnation-item" onclick="nextPage()">下一页</button>
<button class="blog-pagnation-item" onclick="lastPage()">末页</button>
<input type="text" id="jump" style="width: 40px;"/><button class="blog-pagnation-item" onclick="jumpTo()">跳转</button>
</div>
然后接下来我们需要对页面进行初始化,设置好psize和pindex,然后用方法对psize和pindex进行初始化。
var psize=3;//一页中包含的条数
var pindex=1;//页数
function initParam() {
if (getURLParam("psize") != "") {
pszie = getURLParam("psize")
}
if (getURLParam("pindex") != "") {
pindex = getURLParam("pindex");
}
}
initParam();
这样我们就在页面运行之前设置好了psize和pindex ,接下来就是获取页面信息的方法了,这个就需要使用ajax来进行前后端的交互了。
function getAll(){
$.ajax({
url:"/art/getCut",
method:"POST",
data:{
"psize":psize,
"pindex":pindex
},
success:function (result){
if(result.code == 200 && result.data!=null){
//开始拼接
let content = result.data;
console.log(content);
let container = document.querySelector('.container-right');
for (let blog of content) {
if(blog.state==1) {
var tnum
let blogDiv = document.createElement('div');
blogDiv.className = 'blog';
// 创建博客标题
let titleDiv = document.createElement('div');
titleDiv.className = 'title';
titleDiv.innerHTML = blog.title;
blogDiv.appendChild(titleDiv);
// 创建日期
let dateDiv = document.createElement('div');//外层
dateDiv.className = 'date';
//点赞数
let thumbSpan = document.createElement('span');
thumbSpan.className = 'thumbNum';
thumbSpan.innerHTML = "点赞数:"+blog.thumb+" ";
//创建时间
let dateSpan = document.createElement('span');
dateSpan.className = 'data';
dateSpan.innerHTML = blog.updatetime;
//阅读量
let rcountSpan = document.createElement('span');
rcountSpan.className = 'rcount';
rcountSpan.innerHTML = "阅读量:"+blog.rcount+" ";
//拼接
dateDiv.appendChild(dateSpan);
dateDiv.appendChild(thumbSpan);
dateDiv.appendChild(rcountSpan);
blogDiv.appendChild(dateDiv);
// 创建摘要
let descDiv = document.createElement('div');
descDiv.className = 'desc';
descDiv.innerHTML = mySubstr(blog.content);
blogDiv.appendChild(descDiv);
// 创建查看全文按钮
let a = document.createElement('a');
let a2 = document.createElement('a');
let a3 = document.createElement('a');
let thumbBut=document.createElement('button');
a.className = 'detail';
a2.className = 'update';
a3.className= 'comment';
thumbBut.className="thumb";
a.innerHTML = '查看全文 >>';
a2.innerHTML = '修改文章 >>';
a3.innerHTML= '评论 >>';
thumbBut.innerHTML='点赞 >>';
a.href = 'blog_content.html?id=' + blog.id;
a2.href = 'blog_update.html?id=' + blog.id;
a3.href="comment.html?id="+blog.id;
blogDiv.appendChild(a);
blogDiv.appendChild(a2);
blogDiv.appendChild(a3);
blogDiv.appendChild(thumbBut);
// 把 blogDiv 加入外层元素
container.appendChild(blogDiv);
thumbBut.onclick=function (){
$.ajax({
url:"/art/thumb",
data:{
"aid":blog.id
},
success:function (result){
if(result.code == 200 && result.data != null){
console.log(result);
tnum=result.data.thumb;
console.log(tnum)
thumbSpan.innerHTML = "点赞数:"+tnum+" ";
}
}
});
}
}
}
}else {
alert("<h1>暂无数据</h1>");
}
}
})
}
getAll()
Controller:
@RequestMapping("/getCut")
public Object getCut(Integer psize,Integer pindex){
if(psize <= 0 || pindex <=0){
return AjaxRespons.fail(401,-1,"传入数据出错");
}
return articleService.getCut(psize,pindex);
}
Service:
public List<Articleinfo> getCut(Integer psize,Integer pindex){
Integer temp=(pindex-1)*psize;
return articleMapper.getCut(psize,temp);
}
XML:
<select id="getCut" resultType="com.example.xyblog.Model.Articleinfo">
select * from articleinfo limit #{psize} offset #{pindex};
</select>
然后这样我们就完成了页面的拼接,接下来就是首页,这个最简单,只需要回到blog_list这个页面即可。
进入页面先调用initParam,然后再调用getCut方法,不论后面带的参数是什么样的,来到这个页面的执行方法顺序都是如此。
function firstPage(){
location.href="blog_list.html"
}
然后就是上一页和下一页,我们需要在后面加上参数,但是在此之前我们需要计算,然后再进行操作。但是还存在一个问题就是如果一直下一页我如何知道已经到最后一页了呢?
我们用总共的博客数除以psize就可以的到一个数,如果是个整数最好,如果是个小数则取比他大的整数即可。
js:
function getTotal(){
$.ajax({
url:"/art/getTotal",
method: "GET",
data:{
"psize":psize
},
success:function (result){
if(result.code==200 && result.data!=null){
totalPage=result.data;
}
else {
console.log(result.msg);
}
}
})
}
getTotal();
controller;
@RequestMapping("/getTotal")
public Object getTotal(Integer psize){
if(psize <= 0){
return AjaxRespons.fail(401,-1,"传入数据出错");
}
int totalPage=(articleService.geAll().size()/psize)+1;
return totalPage;
}
这样我们就可以得到总页数了。
function prevPage(){
if(pindex==1){
alert("已经是第一页了")
return;
}else{
pindex=parseInt(pindex)-1;
location.href="blog_list.html?pindex="+pindex+"&psize="+psize;
}
}
function nextPage(){
//先要计算总共多少页
if(pindex+1 == totalPage){
alert("已经是最后一页了");
return;
}else{
pindex=parseInt(pindex)+1;
location.href="blog_list.html?pindex="+pindex+"&psize="+psize;
}
}
function lastPage(){
location.href="blog_list.html?pindex="+totalPage+"&psize="+psize;
}
然后跳转就很简单了,我们只需要读取到输入框中的数字即可。
function jumpTo(){
let input=jQuery("#jump");
pindex=input.val();
location.href="blog_list.html?pindex="+pindex+"&psize="+psize;
}
7.点赞
接下来是点赞操作,其实这个点赞操作可以做的更好,但是我当初最开始写的时候没有考虑到重复点赞的问题,所以没有建立单独的点赞表,所以这里暂时没有处理重复点赞的问题,待会讲完我的实现我再讲一下我关于禁止重复点赞的思路。
首先在前端拼接的时候我们需要将点赞的按钮一起拼进去
let thumbBut=document.createElement('button');
thumbBut.className="thumb";
thumbBut.innerHTML='点赞 >>';
然后我们接下来就要在下面直接使用onclick原生的js方法来操作 。
thumbBut.onclick=function (){
$.ajax({
url:"/art/thumb",
data:{
"aid":blog.id
},
success:function (result){
if(result.code == 200 && result.data != null){
console.log(result);
tnum=result.data.thumb;
console.log(tnum)
thumbSpan.innerHTML = "点赞数:"+tnum+" ";
}
}
});
}
}
}
@RequestMapping("/thumb")
public Object thumb(Integer aid){
System.out.println(aid);
if(aid == null){
return AjaxRespons.fail(-1,"id出错");
}
Articleinfo articleinfo=articleService.getBlog(aid);
Integer tnum=articleinfo.getThumb();
tnum++;
articleService.updateThumb(tnum,aid);
articleinfo=articleService.getBlog(aid);
return articleinfo;
}
像上面这样我们就可以实现简单的点赞操作了,但是这个只是一个数字,而且可以重复点赞,所以要是想要避免这个问题可以建立一个点赞表,这个点赞表要和文章关联起来,要有文章id,用户id,如果一个用户点击点赞按钮则需要在表中查找是否存在该用户,如果不存在则允许点赞,如果存在则取消点赞。
8.评论功能
这里实现的评论是一级评论功能,如果多级评论的话就比较复杂了,就需要使用树这个数据结构来进行存储。
同样我们需要在文章下面拼接上一个点赞的按钮,然后跳转到我们的点赞页面。
let a3 = document.createElement('a');
a3.className= 'comment';
a3.innerHTML= '评论 >>';
a3.href="comment.html?id="+blog.id;
然后就是评论页面,这里我做的很简单(其实是css实在是设置不出来了),就是一个输入框和一个按钮。
我们需要建立一个评论类,用来保存评论。
Model:
@Data
public class Comment {
private int id;
private String content;
private String time;
private int uid;
private int aid;
}
然后我们就是要获取到输入框里面的内容了,以及通过url拿到当前的文章ID,这些我们在前端就可以实现。
function subcomment() {
let text = document.getElementById("comment").value;
let id = getURLParam("id");
$.ajax({
url: "/comment/submit",
method: "GET",
data: {
"content": text,
"aid": id
},
success: function (result) {
if (result.code == 200 && result.data != null) {
alert("发布成功");
location.href = "blog_content.html?id=" + id;
} else {
alert(result.msg);
}
},
error: function (err) {
if (err != null && err.status == 401) {
alert("用户未登录,即将跳转到登录页!");
// 已经被拦截器拦截了,未登录
location.href = "/login.html";
}
}
});
}
controller:
@RequestMapping("/submit")
public Object submit(String content, Integer aid, HttpServletRequest request){
if(!StringUtils.hasLength(content)){
return AjaxRespons.fail(-1,"内容出错");
} else if (aid == null) {
return AjaxRespons.fail(-1,"aid无");
}else {
//先通过aid获取uid
HttpSession session= request.getSession(false);
Userinfo userinfo= (Userinfo) session.getAttribute(ConstantVariable.SESSION_KEY_VALUE);
int ret=commentService.updateComment(content,aid,userinfo.getId());
if(ret == 1){
return true;
}else {
return false;
}
}
}
这样我们就完成了评论的保存,接下来就是如何取出来了,我们同样需要拼接,在文章详情页,文章的末尾拼接上文章的评论(略)。
function getComment(){
var id=getURLParam("id");
$.ajax({
url:"/comment/getComment",
method: "POST",
data: {
"aid":id
},
success:function (result){
if(result.code == 200 && result.data != null){
let list=result.data;
let container=document.querySelector(".comment-container");
for(let comment of list){
let commentDiv=document.createElement('div');
commentDiv.className='comment';
let hr=document.createElement('hr');
commentDiv.appendChild(hr);
//姓名(作者id)
let nameDiv=document.createElement('div');
nameDiv.className='name';
nameDiv.innerHTML=comment.aid;
commentDiv.appendChild(nameDiv);
//时间
let dateDiv=document.createElement('div');
dateDiv.className='date';
dateDiv.innerHTML=comment.time;
commentDiv.appendChild(dateDiv);
//内容
let contentDiv=document.createElement('div');
contentDiv.className='content';
contentDiv.innerHTML=comment.content;
commentDiv.appendChild(contentDiv);
container.appendChild(commentDiv);
}
}else{
alert(result.msg)
}
}
});
}
getComment();
9.草稿保存
草稿保存主要就是使用我们的保留字段status,如果status为0则为草稿,反之;文章在两个页面显示,我们需要在个人博客页面中显示出来哪些是草稿哪些不是草稿,在博客列表页展示出非草稿的博客。
9.1个人列表页
首先我们需要给status为0的文章加上草稿的标识以表示区分。
if(blog.state==0){
let titleDiv = document.createElement('div');
titleDiv.className = 'title';
titleDiv.innerHTML = blog.title+"(草稿)";
blogDiv.appendChild(titleDiv);
}else{
这样就可以用来区分哪些是草稿哪些不是草稿了。
9.2博客列表页
在博客列表页中我们需要将那些已经发布的文章展示出来,其实和个人列表页是一样的,只是在这里我们需要将已发布的文章拼接,未发布的文章不拼接。
if(blog.state==1) {
9.3草稿保存
在这里我们使用的页面都是一样的,就是使用发布文章的那一套。
<button onclick="mysub()">发布文章</button>
<button onclick="mysub2()">保存草稿</button>
下面我们只需要设置好mysub2这个函数即可,如果用户点击发布文章,则status为1,反之则status1为0.
@RequestMapping("/draftSave")
public Object draftSave(String title,String content,HttpServletRequest request){
if(StringUtils.hasLength(title) && StringUtils.hasLength(content)){
//有标题有正文,接下来操作数据库
HttpSession session=request.getSession(false);
Userinfo userinfo=(Userinfo) session.getAttribute(ConstantVariable.SESSION_KEY_VALUE);
int ret=articleService.editArt(title,content,userinfo.getId(),0);
return ret;
}else {
return -1;
}
}
10.加密算法
在这之前,我们所有的密码都是明文存储,这样就会导致我们的密码没有安全性,所以我们需要对密码进行加密的处理,我们可以采用MD5来进行加密,但是MD5加密是可以被破解的,所以我们需要一个随机值和我们的密码拼起来,再进行加密,这个随机值我们称之为盐值,这样我们就可以加强密码的安全性,但是加密是加了,如何解密呢,所以我们需要将盐值也保存到文们的数据库中,所以我们需要增加我们数据库的长度,用来存储盐值,所以我们用前32位存储盐值,后32位存储密码的md5加密后的值。
public static String encryption(String password){
String salt= UUID.randomUUID().toString().replace("-","");
//加密后的密码
String en_password= DigestUtils.md5DigestAsHex((salt+password).getBytes());
return salt+en_password;
}
这个主要是用于用户注册的时候,那么用户登陆的时候我们又怎样将用户输入的密码和加密后的 密码进行比对呢?这时候我们需要将数据库中的前32位拿出来,然后和用户输入的密码进行拼接,然后对这一串字符串进行md5加密,然后再和我们数据库中的密码进行比对即可。
/**
* 解码
* @param password 用户输入的passwor
* @param right_password 数据库里的password
* @return
*/
public static boolean decryption(String password,String right_password){
if(right_password.length()!=64){
return false;
}
if(!StringUtils.hasLength(password) && !StringUtils.hasLength(right_password)){
return false;
}
//拿到盐值
String salt=right_password.substring(0,32);
//对用户的输入密码进行加密
String en_password=DigestUtils.md5DigestAsHex((salt+password).getBytes());
if((salt+en_password).equals(right_password)){
return true;
}else{
return false;
}
}
11.结语
那么说到这里,博客系统就差不多说完了,其实还有很多可以改进和添加的地方,比如说个人主页等等,这些日后也会慢慢完善的,那就说到这里啦,下次见!!!