博客项目
文章目录
- 1. 创建项目
- 2. 数据库设计
- 3. 前置任务
- 3.1 拦截器
- 3.2 统一数据格式
- 3.3 创建一个 Constant
- 3.4 统一异常处理
- 3.5 密码加密
- 4. 功能实现
- 4.1 登录功能
- 4.2 注册功能
- 4.3 博客列表页 (功能实现)
- 4.3.1 左侧框
- 4.3.2 右侧框 (分页功能 + 页面显示)
- 4.4 博客详情页
- 4.5 写博客功能
- 4.6 注销功能
博客系统
前言 : 本文主要 是通过 ssm 搭配之前的 博客系统页面, 来完成一个小项目 .
项目完整代码
1. 创建项目
使用到的技术 :
- 后端 : Spring Boot + Spring MVC + MyBatis + 拦截器 / 统一异常处理 + 统一数据返回 (Spring AOP)
- 前端 : HTML + CSS + javaScript + jquery
2. 数据库设计
通过我们需要实现的功能 ,能够知道 需要两张表 , 第一张表 用户表 ,用来完成登录 注册 注销等功能 , 第二张表 用来存储 博客 , 查看博客等 .
1. 创建 用户列表
-- 如果存在 这个数据库就删除
drop
database if exits mywebsite;
-- 创建数据库
create
database mywebsite;
-- 选中数据库
use
mywebsite
-- 创建用户表
create table user
(
-- 系统分配
id int primary key auto_increment,
-- 必填
username varchar(255) not null,
password varchar(255) not null,
-- 非必填
qq varchar(255) default '',
address varchar(255) default '',
crateTime datetime default now(),
sex varchar(2) default '男',
-- url 用来存放 用户 头像 图片 如果用户没有上传就使用默认的 .
url varchar(1024) default '阳台.png'
);
2. 创建 blog 表
create table blog
(
blogId int primary key auto_increment,
title varchar(1024) not null,
-- 这里 一篇博客的内容可能非常多 使用 varchar可能不够 ,这里就是用 mediumtext
content mediumtext,
-- 用户 id
userid int,
-- 发布时间
postTime datetime default now(),
-- 类型
type varchar(255) not null,
);
表创建好了 ,下面就可以完成一些 准备工作 ,比如 配置好环境 , 写好 拦截器 ,统一数据格式 等 .
这里统一数据格式 可以写一个类 , 通过这个类来返回 或者 通过 @ControllerAdvice + ResponseBodyAdvice 来完成 , 这里我会使用 写一个类来返回信息 .
3. 前置任务
这里先来完成 拦截器 , 统一异常处理 , 统一数据格式 .
application.yml
# 配置当前运行的环境 (配置文件)
# spring > profiles > active
spring:
profiles:
active: dev # 使用开发环境的配置文件
# 配置 mybatis xml 保存路径
mybatis:
mapper-locations: classpath:mybatis/**Mapper.xml
# 在公共 yml 文件 来 配置 mybatis 的保存路径
application-dev.yml
# 开发环境的配置文件
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mywebsite?characterEncoding=utf8
username: root
password: 1234
driver-class-name: com.mysql.cj.jdbc.Driver # 在 8.0 之前 是没有点 jc的 -> com.mysql.jdbc.Driver
# 设置日志级别
logging:
level:
com:
example:
usermanager: debug
# 对具体类机型日志级别设定
# 开启 MyBatis SQL 打印
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3.1 拦截器
这里我们实现拦截器 主要有两部 : 1. 自定义拦截器 , 2. 给拦截器设置规则 (那些 需要拦截 , 那些不需要拦截)
附上代码 :
AppConfig 类
package com.example.usermanager.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 系统配置文件
*/
@Configuration
public class AppConfig implements WebMvcConfigurer {
// 注入拦截器
@Autowired
private LoginIntercept loginIntercept;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginIntercept).
addPathPatterns("/**")
.excludePathPatterns("/user/login")
.excludePathPatterns("/insert")
.excludePathPatterns("/css/**")
.excludePathPatterns("/fonts/**")
.excludePathPatterns("/images/**")
.excludePathPatterns("/js/**")
.excludePathPatterns("/login.html");
}
}
LoginIntercept
package com.example.blog_ssm.config;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* 自定义拦截器
*/
@Component
public class LoginIntercept implements HandlerInterceptor {
/**
* true 表示已经登录 ,会继续访问目标方法
* false 表示未登录 , 跳转到登录页面
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// false : 如果 没有 session 也不会创建
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("user") != null) {
// 表示登录成功
return true;
}
// 403 当前你没有资格访问
response.setStatus(403);
// 重定向
response.sendRedirect("/login.html");
return false;
}
}
拦截器 弄好了 , 我们可以看看效果 :
xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.blog_ssm.mapper.UserMapper">
</mapper>
3.2 统一数据格式
1. 创建一个类用来 统一 数据格式
附上代码 :
package com.example.blog_ssm.util;
import lombok.Data;
/**
* 用来统一数据格式
*
* @param <T>
*/
@Data
public class ResponseBodyMessage<T> {
// 1. 状态码
private Integer status;
// 2. 信息描述
private String message;
// 3. 数据
private T data;
public ResponseBodyMessage(Integer status, String message, T data) {
this.status = status;
this.message = message;
this.data = data;
}
}
2. 使用注解
3.3 创建一个 Constant
之前我们写拦截器的使用 ,通过 session 中的key 获取 user 对象时 ,写了一个 “user” , 这里可以使用一个类 ,在类里面写一个 常量 ,然后 只需要通过这个 来获取 user 即可 。
3.4 统一异常处理
代码 :
package com.example.blog_ssm.config;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.HashMap;
/**
* 统一异常的拦截处理类
*/
@RestControllerAdvice
// 使用 @ControllerAdvice 需要再加一个注解 @ResponseBody (返回一个非静态页面)
public class MyExceptionAdvice {
@ExceptionHandler(Exception.class)
public Object exceptionAdvice(Exception e) {
HashMap<String, Object> result = new HashMap<>();
result.put("status", -1);
result.put("message", "程序异常 : " + e.getMessage());
result.put("data", "");
return result;
}
}
3.5 密码加密
在 util 包内 创建 PasswordUtil 类
package com.example.blog_ssm.util;
import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.SecureUtil;
import org.springframework.util.StringUtils;
/**
* 密码工具类
*/
public class PasswordUtil {
/**
* 1. 加密 (加盐)
*/
public String encrypt(String password) {
// 密码 : 随机盐值 + 密码
String salt = IdUtil.simpleUUID();
String finalPassword = SecureUtil.md5(salt + password);
return salt + "$" + finalPassword;
}
/**
* 解密
*
* @param password 要验证的密码 (未加密)
* @return 数据库中的加了盐值的密码
*/
public boolean decrypt(String password, String securePassword) {
boolean result = false;
if (StringUtils.hasLength(password) && StringUtils.hasLength(securePassword)) {
// 注意 : $ 是特殊字符 , 使用 split 分割时 需要转移
if (securePassword.length() == 65 && securePassword.contains("$")) {
// 随机盐值 为 32 , md5 加密的 密码 32 加上 $ 1字符 总共 65 字符
String[] securePasswordArr = securePassword.split("\\$");
// 盐值
String salt = securePasswordArr[0];
// 根据盐值 加密的密码
String finalPassword = securePasswordArr[1];
// 根据盐值 对新的密码进行加密
password = SecureUtil.md5(salt + password);
// 进行对比
if (finalPassword.equals(password)) {
result = true;
}
}
}
return result;
}
}
将 工具类 交给 spring 管理 ,后面使用 只需要注入即可 .
到此我们就完成了前置任务, 下面来写我们的功能
4. 功能实现
4.1 登录功能
约定一下 : 请求和响应
请求 :
[
{
post, (登录一般使用 post)
/user/login
data:{
username : "张三",
password :"1234"
}
}
]
响应 :
[
{
"status" : 1 / -1 (1 表示成功 , -1 表示失败) .
"message" : "登录成功" / "登录失败",
"data" : true / false
}
]
图一 :
图二 :
此时 后端就完成了 下面就可以来写前端了 :
图一 :
图二 :
代码 :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
<link rel="stylesheet" href="./css/common.css">
<link rel="stylesheet" href="./css/login.css">
<script src="./js/jquery.js"></script>
</head>
<body>
<!-- 导航栏 -->
<div class="nav">
<img src="./imgs/阳台.png">
<span class="title">我的博客系统</span>
<!-- 这个标签仅仅用于占位 ,把下面几个a 标签挤到右边-->
<div class="spacer"></div>
<a href="#">主页</a>
<a href="#">写博客</a>
</div>
<!--
正文部分
这个 login-container 是贯穿整个页面的容器
-->
<div class="login-container">
<!-- 垂直水平居中的登录对话框 -->
<div class="login-dialog">
<h3>登录</h3>
<div class="row">
<span>用户名</span>
<input type="text" id="username" placeholder="输入用户名">
</div>
<div class="row">
<span>密码</span>
<input type="password" id="password" placeholder="输入密码">
</div>
<div class="row">
<button id="submit">提交</button>
</div>
<div class="row">
<a id="insert" href = "add.html">
注册
</a>
</div>
<div class="a">没有账户? 点击上面进行注册</div>
</div>
</div>
<script>
// 通过 id 选中输入框 ,
let username = document.querySelector("#username");
// 通过 id 选中密码框
let password = document.querySelector("#password");
let submit = document.querySelector("#submit");
// 点击按钮后触发
submit.onclick = function () {
// 1. 判断 username or password
if (jQuery.trim(username.value) === '') {
alert("请先输入用户名");
}
if (jQuery.trim(password.value) === '') {
alert("请先输入密码")
}
// 此时 用户 和密码 都有了 ,发送 请求
$.ajax({
url: "/user/login",
type: "POST",
data: {
"username": username.value,
"password": password.value
},
// 回调函数
success: function (result) {
if (result != null && result.data.status > 0) {
// 登录成功 , 跳转到 博客列表页
location.href = "blog_list.html";
} else {
//登录 登录失败 ,
alert("登录失败,请重新输入")
}
}
})
}
</script>
</body>
</html>
登录功能搞完 : 下面就可以来弄 注册功能
4.2 注册功能
约定一下请求和响应 :
请求:
[
{
post,
url : /user/add
data:{
必填
"username" : xxx,
"password" : xxx,
非必填
"address" : xxx,
......
}
}
]
响应 :
[
{
data:{
"status" : 1 / -1 (注册成功 / 注册失败)
"message" : "",
"data" : true / false
}
}
]
图一 :
图二 :
图三 :
图四 :
附上代码 :
/**
* 2. 注册功能
*/
@RequestMapping(value = "/add")
@Transactional
public ResponseBodyMessage<Boolean> addUser(User user, @RequestPart(required = false, value = "filename") MultipartFile file) {
if (user == null) {
return new ResponseBodyMessage(-1, "注册失败", false);
}
// 1. 判断 必填参数是否为空 (这里可以不写 ,前端大概率 是会判断的 。 )
if ("".equals(user.getUsername())) {
return new ResponseBodyMessage<>(-1, "注册失败, 当前用户为输入用户名", false);
}
if ("".equals(user.getPassword())) {
return new ResponseBodyMessage<>(-1, "注册失败,当前用户未输入密码", false);
}
// 2. 校验用户名的 唯一性 : 如果 用户名已经纯在了 那么就不能注册
User user2 = userService.getUserByUserName(user.getUsername());
if (user2 != null) {
return new ResponseBodyMessage<>(-1, "注册失败, 用户名已存在", false);
}
// 3. 手动设置 为 '' 的数据
if ("".equals(user.getAddress())) {
user.setAddress(null);
}
if ("".equals(user.getQq())) {
user.setQq(null);
}
if ("".equals(user.getSex())) {
user.setSex(null);
}
if ("".equals(user.getUrl())) {
user.setUrl(null);
}
// 4. 对密码进行加密操作
user.setPassword(passwordUtil.encrypt(user.getPassword()));
// 5. 如果用户 上传了头像 ,可以将图片存入到本地
if (file != null) {
// 此时上传了头像 :
// 获取到文件名 + 类型
String fileNameAndType = file.getOriginalFilename();
// 比如文件名为 : 阳台.png , 此时可以获取到 . 的 下标
int index = fileNameAndType.lastIndexOf(".");
// 从 index 位置开始截取
String postfix = fileNameAndType.substring(index);
// 判断一下 图片的格式是否符合预期要求
if (".jpg".equals(postfix) || ".png".equals(postfix)) {
// 通过 uuid 来设置文件名
String uuid = IdUtil.simpleUUID();
String imgFileStr = uuid + postfix;
// 创建文件
String path = IMAGE_PATH + imgFileStr;
File imgFile = new File(path);
if (!imgFile.exists()) {
imgFile.mkdir();
}
try {
// 指定图片 , 上传之后的存储位置
file.transferTo(imgFile);
// 文件上传成功 :
user.setUrl(imgFileStr);
userService.addUser(user);
return new ResponseBodyMessage<>(1, "注册成功", true);
} catch (IOException e) {
// e.printStackTrace();
// 如果 创建失败 , 手动事务回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
// 此时 注册失败
return new ResponseBodyMessage<>(-1, "注册失败", false);
} else {
return new ResponseBodyMessage<>(-1, "图片格式有误", false);
}
}
// 6. 调用 userService 中的 addUser 方法 进行用户添加 (此时未上传图片 , 图片 为 默认)
Integer ret = userService.addUser(user);
if (ret != 1) {
return new ResponseBodyMessage<>(-1, "注册失败", false);
}
return new ResponseBodyMessage<>(1, "注册成功", true);
}
后端写完, 来完成我们的前端 :
之前并没有完成 注册页面 , 这里直接来拷贝一下
1. add.html
<!DOCTYPE html>
<!-- 网页使用的语言 -->
<html lang="zh-CN">
<head>
<!-- 指定字符集 -->
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>添加用户</title>
<link href="css/bootstrap.min.css" rel="stylesheet">
<style>
body {
background-image: url("./images/阳台.png");
background-repeat: no-repeat;
background-size: cover;
}
</style>
</head>
<body>
<div class="container" style="width: 400px;">
<h3 style="text-align: center;">添加用户</h3>
<div class="form-group">
<label for="loginname">登录名:</label>
<input type="text" class="form-control" id="loginname" name="username" placeholder="请输入登录名"/>
</div>
<div class="form-group">
<label for="username">姓名:</label>
<input type="text" class="form-control" id="username" name="name" placeholder="请输入姓名"/>
</div>
<div class="form-group">
<label for="password">密码:</label>
<input type="password" class="form-control" id="password" name="password" placeholder="请输入密码"/>
</div>
<div class="form-group">
<label for="password2">确认密码:</label>
<input type="password2" class="form-control" id="password2" name="password" placeholder="请输入密码"/>
</div>
<div class="form-group">
<label>性别:</label>
<input id="man" type="radio" name="sex" value="男" checked="checked"/>男
<input id="women" type="radio" name="sex" value="女"/>女
</div>
<div class="form-group">
<label for="age">年龄:</label>
<input type="number" class="form-control" id="age" name="age" placeholder="请输入年龄"/>
</div>
<div class="form-group">
<label for="address">籍贯:</label>
<select name="address" id="address" class="form-control">
<option value="北京">北京</option>
<option value="上海">上海</option>
<option value="广州">广州</option>
<option value="深圳">深圳</option>
<option value="成都">成都</option>
<option value="杭州">杭州</option>
<option value="重庆">重庆</option>
<option value="西安">西安</option>
<option value="武汉">武汉</option>
<option value="沧州">沧州</option>
<option value="江西">江西</option>
</select>
</div>
<div class="form-group">
<label for="qq">QQ:</label>
<input type="text" id="qq" class="form-control" name="qq" placeholder="请输入QQ号码"/>
</div>
<div class="form-group">
<label for="email">Email:</label>
<input type="text" id="email" class="form-control" name="email" placeholder="请输入邮箱地址"/>
</div>
<!-- style="display: none;"-->
<div class="form-group" id="adminDiv">
<label for="email">管理员:</label>
<input id="admin_yes" type="radio" name="isadmin" value="1"/>是
<input id="admin_no" type="radio" name="isadmin" value="0" checked="checked"/>否
</div>
<div class="form-group" style="text-align: center">
<input id="btn_sub" class="btn btn-primary" type="button" value="提交" onclick="mysub()"/>
<input id="btn_back" class="btn btn-default" type="button" value="返回" onclick="location.href='list.html'"/>
</div>
</div>
</body>
</html>
用到的 css 可以到 我 的资源里面下载 出来 , 比较多 这里就不拷贝到上面 了 .
这里我们需要使用 FormData 来 发送我们的数据 可以看来看一下这 : FormData
代码 :
<!DOCTYPE html>
<!-- 网页使用的语言 -->
<html lang="zh-CN">
<head>
<!-- 指定字符集 -->
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>添加用户</title>
<link href="./css/bootstrap.min.css" rel="stylesheet">
<script src="./js/jquery.js"></script>
<style>
body {
background-image: url("./imgs/阳台.png");
background-repeat: no-repeat;
background-size: cover;
}
#image {
width: 400px;
height: 250px;
border: 1px solid #eee;
}
.message {
width: 110px;
height: 50px;
line-height: 50px;
font-weight: 600;
}
</style>
</head>
<body>
<form enctype="multipart/form-data" id="form1">
<div class="container" style="width: 400px;">
<h3 style="text-align: center;">添加用户</h3>
<div class="form-group">
<label for="username">姓名:</label>
<input type="text" class="form-control" id="username" name="name" placeholder="请输入姓名"/>
</div>
<div class="form-group">
<label for="password">密码:</label>
<input type="password" class="form-control" id="password" name="password" placeholder="请输入密码 "/>
</div>
<div class="form-group">
<label for="password2">确认密码:</label>
<input type="password2" class="form-control" id="password2" name="password" placeholder="请输入密码"/>
</div>
<div class="form-group">
<label>性别:</label>
<input id="man" type="radio" name="sex" value="男" checked="checked"/>男
<input id="women" type="radio" name="sex" value="女"/>女
</div>
<div class="form-group">
<label for="address">籍贯:</label>
<select name="address" id="address" class="form-control">
<option value="">可以不选择</option>
<option value="北京">北京</option>
<option value="上海">上海</option>
<option value="广州">广州</option>
<option value="深圳">深圳</option>
<option value="成都">成都</option>
<option value="杭州">杭州</option>
<option value="重庆">重庆</option>
<option value="西安">西安</option>
<option value="武汉">武汉</option>
<option value="沧州">沧州</option>
<option value="江西">江西</option>
</select>
</div>
<div class="form-group">
<label for="qq">QQ:</label>
<input type="text" id="qq" class="form-control" name="qq" placeholder="请输入QQ号码 (非必填) "/>
</div>
<div class="form-group">
<input type="file" name="filename" id="imgFile">
<span class="message">图片样式: </span>
<img src="" id="image"/>
</div>
<div class="form-group" style="text-align: center">
<input id="btn_sub" class="btn btn-primary" type="button" value="提交"/>
<input id="btn_back" class="btn btn-default" type="button" value="返回" onclick="location.href='list.html'"/>
</div>
</div>
</form>
<!--
// 拿到 input type 为 radio 中的内容 即 获取 男 女
let sex = $('input[name=sex]:checked').val();
let address = $("#address").val();
-->
<script>
let imgFile = document.querySelector("#imgFile");
// 这一部分 : 当我们上传图片后 , 我们的 图片样式 就会将图片显示出来
imgFile.onchange = function () {
let img = document.querySelector("#image");
let image = imgFile.files[0];
// let formData = new FormData();
if (image) {
// formData.append('filename', image);
img.src = window.URL.createObjectURL(image);
}
}
// 当点击 提交按钮后 构造数据 , 通过 ajax 发送请求给后端
let submit = document.querySelector("#btn_sub");
submit.onclick = function () {
let username = document.querySelector("#username");
let password = document.querySelector("#password");
let password2 = document.querySelector("#password2");
// 通过 jquery 拿到 性别框里面的内容
let sex = jQuery('input[name=sex]:checked').val();
let address = document.querySelector('#address').value;
let qq = document.querySelector("#qq").value;
// jQuery.trim 去掉 前后空格
if (jQuery.trim(username.value) === '') {
alert("请先输入用户名!!")
// 将焦点设置到 id 为 username 的输入 框上
username.focus();
return false;
}
if (jQuery.trim(password.value) === '') {
alert("请先输入密码");
password.focus();
return false;
}
if (jQuery.trim(password2.value) === '') {
alert("请输入确认密码");
password2.focus();
}
if (password.value !== password2.value) {
alert("两次密码不同,请重新输入")
password.focus();
return false;
}
// 使用 formData 类来返回
let formData = new FormData();
formData.append('username', username.value);
formData.append('password', password.value);
formData.append('qq', qq);
formData.append('sex', sex);
formData.append('address', address);
let img = document.querySelector("#image");
// 获取文件
let image = imgFile.files[0];
// 这里也可以通过 image 判断
if (img.src === '') {
console.log('未上传图片 !!! ');
formData.append('filename', null);
} else {
formData.append('filename', image)
}
// 通过 ajax 发送请求
jQuery.ajax({
type: "POST",
url: "/user/add",
data: formData,
processData: false,
contentType: false,
success: function (result) {
if (result != null && result.data.status > 0) {
alert('注册成功!');
location.href = "login.html";
}else {
alert('注册失败')
}
},
error : function(){
alert("出错了, 请稍后再试!!!")
}
})
}
</script>
</body>
</html>
上传文件使用到的 input 标签, 和 使用到的change 事件 : input 标签 change 事件
效果 :
到此我们的注册功能就完成了 , 下面就来写我们的博客列表页 .
4.3 博客列表页 (功能实现)
4.3.1 左侧框
图一 :
AppConfig 类
package com.example.blog_ssm.config;
import com.example.blog_ssm.util.PasswordUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class AppConfig implements WebMvcConfigurer {
// 1. 注入拦截器
@Autowired
private LoginIntercept loginIntercept;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/product/**").addResourceLocations("file:D:/ret/");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginIntercept)
.addPathPatterns("/**")
.excludePathPatterns("/user/login")
.excludePathPatterns("/user/add")
.excludePathPatterns("/css/**")
.excludePathPatterns("/js/**")
.excludePathPatterns("/imgs/**")
.excludePathPatterns("/login.html")
.excludePathPatterns("/add.html")
.excludePathPatterns("/product/**");
}
@Bean
public PasswordUtil passwordUtil() {
return new PasswordUtil();
}
}
图二 :
后端完成了 ,下面就可以来完成我们的前端 :
图三 :
图四 :
图五 :
图六 :
图七 :
<script>
// 这个 ajax 获取登录状态 :
jQuery.ajax({
type: "GET",
url: "/user/getuser",
success: function (result) {
if (result != null && result.data.status > 0) {
let image = document.querySelector("#image1");
image.src = "product/" + result.data.data.url;
let image2 = document.querySelector("#image2");
image2.src = "product/" + result.data.data.url;
// 将用户名 换上去
let username = document.querySelector("#username");
username.innerHTML = result.data.data.username;
}
}
})
// 这个 ajax 获取 分类
jQuery.ajax({
type: "GET",
url: "/blog/gettype",
success: function (result) {
if (result != null && result.data.status > 0) {
let number1 = document.querySelector("#number1");
let number2 = document.querySelector("#number2");
number1.innerHTML = result.data.data[0]
number2.innerHTML = result.data.data[1];
} else {
alert("分类设置失败 !!! ")
}
},
error: function () {
alert("出错了, 请稍后再试!!!")
}
})
</script>
左侧部分 就完成了, 下面就来完成我们的右侧部分 ,这里我们可以写一个分页器
4.3.2 右侧框 (分页功能 + 页面显示)
图一 :
图二 :
图三 :
图四 :
这里不好截图 直接看代码 :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>博客列表页</title>
<link rel="stylesheet" href="./css/common.css">
<link rel="stylesheet" href="./css/blog_list.css">
<script src="./js/jquery.js"></script>
<link href="css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<!-- 导航栏 -->
<div class="nav">
<img src="./imgs/阳台.png" id="image1">
<span class="title">我的博客系统</span>
<!-- 这个标签仅仅用于占位 ,把下面几个a 标签挤到右边-->
<div class="spacer"></div>
<a href="#">主页</a>
<a href="#">写博客</a>
<a href="#">注销</a>
</div>
<!-- 页面主体部分 -->
<div class="container">
<!-- 左侧信息-->
<div class="container-left">
<!-- 使用 这个 .card 表示用户信息-->
<div class="card">
<img src="./imgs/girl.png" alt="图片显示失败" id="image2">
<!-- 用户名-->
<h3 id="username"></h3>
<a href="#">Gitee 地址</a>
<div class='counter'>
<span>文章</span>
<span>分类</span>
</div>
<div class="counter">
<span id="number1">1</span>
<span id="number2">2</span>
</div>
</div>
</div>
<!-- 右侧信息-->
<div class="container-right">
<!-- <!–-->
<!-- 表示一篇博客-->
<!-- –>-->
<!-- <div class="blog">-->
<!-- <!–-->
<!-- 博客标题-->
<!-- –>-->
<!-- <div class="title">我的第一篇博客</div>-->
<!-- <!–-->
<!-- 发布时间-->
<!-- –>-->
<!-- <div class="data">-->
<!-- 2023-03-02-->
<!-- </div>-->
<!-- <!–-->
<!-- 博客的摘要-->
<!-- –>-->
<!-- <div class="desc">-->
<!-- 从今天起 , 我要认真敲代码-->
<!-- Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sint eaque facilis perferendis! Numquam-->
<!-- neque voluptatum ab vero expedita possimus fuga eos, illo sapiente delectus quidem natus maiores,-->
<!-- ipsum impedit rerum?-->
<!-- </div>-->
<!-- <!–-->
<!-- 查看全文按钮-->
<!-- –>-->
<!-- <a href="#">查看全文 >></a>-->
<!-- </div>-->
<div id="page">
<nav aria-label="Page navigation">
<ul id="all" class="pagination">
<li class="active"><a href="javascript:firstPage();">首页</a></li>
<li><a href="javascript:beforePage();">上一页</a></li>
<li><a href="javascript:nextPage();">下一页</a></li>
<li><a href="javascript:lastPage();">末页</a></li>
<span id="pageinfo" style="font-size: 20px;margin-left: 10px;">
</span>
</ul>
</nav>
</div>
</div>
</div>
<script>
// 这个 ajax 获取登录状态 :
jQuery.ajax({
type: "GET",
url: "/user/getuser",
success: function (result) {
if (result != null && result.data.status > 0) {
let image = document.querySelector("#image1");
image.src = "product/" + result.data.data.url;
let image2 = document.querySelector("#image2");
image2.src = "product/" + result.data.data.url;
// 将用户名 换上去
let username = document.querySelector("#username");
username.innerHTML = result.data.data.username;
}
}
})
// 这个 ajax 获取 分类
jQuery.ajax({
type: "GET",
url: "/blog/gettype",
success: function (result) {
if (result != null && result.data.status > 0) {
let number1 = document.querySelector("#number1");
let number2 = document.querySelector("#number2");
number1.innerHTML = result.data.data[0]
number2.innerHTML = result.data.data[1];
} else {
alert("分类设置失败 !!! ")
}
},
error: function () {
alert("出错了, 请稍后再试!!!")
}
})
// 分页功能 :
// 1. 当前的页码
let pIndex = 1;
// 2. 每页显示多少篇博客
let pSize = 2;
// 3. 总页数
let totalPage = 0;
// 4. 总条数 (当前所有的博客数目)
let totalCount = 0;
// 通过 ajax 获取 总页数 和 总条数
function getList() {
jQuery.ajax({
type: "GET",
url: "/blog/listbypage",
data: {
"pIndex": pIndex,
"pSize": pSize
},
success: function (result) {
if (result != null && result.data.status > 0) {
// 总博客数
totalCount = result.data.data.count;
// ceil 四舍五入并返回大于等于给定数字的最小整数。
totalPage = Math.ceil(parseInt(totalCount) / pSize);
let size = result.data.data.list.length;
// rightDiv 后面创建的 元素需要挂载 rightDiv 上
let rightDiv = document.querySelector(".container-right");
for (let i = 0; i < size; i++) {
let ret = result.data.data.list[i];
let blogDiv = document.createElement('div');
// 引入 class 属性
blogDiv.className = "blog";
// 1. 构造标题
let titleDiv = document.createElement('div');
titleDiv.innerHTML = ret.title;
titleDiv.className = "title";
// 将 titleDiv 挂到 blogDiv上
blogDiv.appendChild(titleDiv);
// 2. 构造 发布时间
let dataDiv = document.createElement('div');
dataDiv.innerHTML = ret.postTime;
dataDiv.className = "data";
blogDiv.appendChild(dataDiv);
// 3. 构造 文章描述
let descDiv = document.createElement('div');
descDiv.innerHTML = ret.content;
descDiv.className = "desc";
blogDiv.appendChild(descDiv);
// 4. 构造 查看全文按钮
let a = document.createElement('a');
a.innerHTML = "查看全文 >>";
// 重点 : 这里我们点击查看全文 跳转到 博客详情页 ,这里可以 在 url 里面添加一个 博客id ,
// 后面在 博客详情页就可以通过这个 blogId 获取到文章.
a.href = "blog_detail.html?blogId=" + ret.blogId;
blogDiv.appendChild(a);
rightDiv.appendChild(blogDiv);
}
// 将 分页器 挂载 构造好的数据 最后面 .
let page = document.querySelector("#page");
rightDiv.appendChild(page);
} else {
alert("获取失败!!!")
}
},
error: function () {
alert("出错了,请稍后在尝试!!!")
}
})
}
// getList();
// 首页
function firstPage() {
location.href = "blog_list.html?pIndex=1"
}
// 上一页
function beforePage() {
if (pIndex > 1) {
pIndex = parseInt(pIndex) - 1;
location.href = "blog_list.html?pIndex=" + pIndex;
} else {
alert("已经是首页了!!!")
}
}
// 下一页
function nextPage() {
if (pIndex < totalPage) {
pIndex = parseInt(pIndex) + 1;
location.href = "blog_list.html?pIndex=" + pIndex;
} else {
alert("已经是末页了!!!")
}
}
// 末页
function lastPage() {
location.href = "blog_list.html?pIndex=" + totalPage;
}
// 使用这个 方法来初始话页面
function initPage() {
// 获取 当前页面的 查询字符串 比如 :?pIndex=2
let url = location.search;
if (url != '') {
// 将 问好 去掉 此时就剩下了 pIndex=2 (假设页数是2)
url = url.substring(1);
// let kvs = url.split("&");
let kvs = url.split("=");
// 获取到 跳转后的页数 ,
pIndex = kvs[1];
}
// 通过 getList 方法去获取到数据
getList();
}
// 通过 initPage 方法来 初始化页面 .
initPage();
</script>
</body>
</html>
Math.ceil() - JavaScript | MDN (mozilla.org)
页面效果:
上面我们的代码其实还有一个没中不足的地方 , 我们的文章 应该是 后发布的在前面, 而不是 以前发布的在前面 ,这里就来修改一下 ,也非常简单 , 就是给我们的SQL 加一个 排序 (按照时间排序即可)
到此我们的博客列表页的内容就完成了 , 下面就来完成我们的 博客详情页
4.4 博客详情页
这里我们主要实现查看 博客的功能 .
图一 :
图二 :
图三 :
图四 :
图五 :
图六 :
图七 :
前端代码 :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>博客详情页</title>
<link rel="stylesheet" href="./css/common.css">
<link rel="stylesheet" href="./css/blog_detail.css">
<script src="./js/jquery.js"></script>
<!-- 引入 editor.md 的依赖-->
<link rel="stylesheet" href="./editor.md/css/editormd.min.css">
<script src="./editor.md/lib/marked.min.js"></script>
<script src="./editor.md/lib/prettify.min.js"></script>
<script src="./editor.md/editormd.min.js"></script>
</head>
<body>
<!-- 导航栏 -->
<div class="nav">
<img src="./imgs/阳台.png">
<span class="title">我的博客系统</span>
<!-- 这个标签仅仅用于占位 ,把下面几个a 标签挤到右边-->
<div class="spacer"></div>
<a href="#">主页</a>
<a href="#">写博客</a>
<a href="#">注销</a>
</div>
<!-- 页面主体部分 -->
<div class="container">
<!-- 左侧信息-->
<div class="container-left">
<!-- 使用 这个 .card 表示用户信息-->
<div class="card">
<img src="./imgs/girl.png" alt="图片显示失败">
<!-- 用户名-->
<h3>牧</h3>
<a href="#">Gitee 地址</a>
<div class='counter'>
<span>文章</span>
<span>分类</span>
</div>
<div class="counter">
<span>1</span>
<span>2</span>
</div>
</div>
</div>
<!-- 右侧信息-->
<div class="container-right">
<!-- 博客标题 -->
<h3 class="title" id="title">我的第一篇博客</h3>
<!-- 博客发布时间-->
<div class="date">2023-03-02</div>
<!-- 博客正文 -->
<div id="content">
<!-- <P>-->
<!-- 从今天开始我要认真敲代码-->
<!-- 从今天开始我要认真敲代码-->
<!-- 从今天开始我要认真敲代码-->
<!-- 从今天开始我要认真敲代码-->
<!-- </P>-->
<!-- <P>-->
<!-- 从今天开始我要认真敲代码-->
<!-- 从今天开始我要认真敲代码-->
<!-- 从今天开始我要认真敲代码-->
<!-- 从今天开始我要认真敲代码-->
<!-- </P>-->
<!-- <P>-->
<!-- 从今天开始我要认真敲代码-->
<!-- 从今天开始我要认真敲代码-->
<!-- 从今天开始我要认真敲代码-->
<!-- 从今天开始我要认真敲代码-->
<!-- </P>-->
</div>
</div>
</div>
<script>
function getBlogDetail() {
jQuery.ajax({
type: "GET",
// location.search 就是 ?blogId=x
url: "/blog/getblog" + location.search,
success: function (result) {
if (result != null && result.data.status > 0) {
let data = result.data.data;
// 1. 构造博客标题
let title = document.querySelector("#title");
title.innerHTML = data.title;
// 2. 构造发布时间
let dateDiv = document.querySelector(".date");
dateDiv.innerHTML = data.postTime;
// 3. 构造正文部分
// let content = document.querySelector(".content");
//
// content.innerHTML = data.content;
// 使用 editormd.md 自带的方法 对内容进行渲染
editormd.markdownToHTML('content', {
markdown: data.content
})
let userid = data.userid;
jQuery.ajax({
type: "GET",
url: "/user/getuserbyid",
data: {
"userid": userid,
},
success: function (result) {
if (result != null && result.data.status > 0) {
let data = result.data.data;
// 通过子类选择器选中 img 元素
let img = document.querySelector(".card>img");
img.src = "product/" + data.user.url;
let title = document.querySelector(".card>h3");
title.innerHTML = data.user.username;
let spanArr = document.querySelectorAll(".counter>span")
spanArr[2].innerHTML = data.blogNumber;
spanArr[3].innerHTML = data.type;
} else {
alert("设置错误!!!")
}
},
error: function () {
alert("出错了,请稍后再试!!")
}
})
}
}
})
}
getBlogDetail();
function getLoginUser() {
jQuery.ajax({
type: "GET",
url: "/user/getuser",
success: function (result) {
if (result != null && result.data.status > 0) {
let img = document.querySelector(".nav>img");
img.src = "product/" + result.data.data.url;
}
}
})
}
getLoginUser();
</script>
</body>
</html>
4.5 写博客功能
图一 :
图二 :
图三 :
图四 :
图五 :
最后完成我们的注销 功能 这个 小项目就完成了 .
4.6 注销功能
图一 :
图二 :
到此 这个 小项目就完成了, 其实这个项目还有很多东西可以加 ,这些大家都可以 自由发挥 .