水资源监控能力建设 网站/北京网站优化seo
文章目录
- 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可能不够 ,这里就是用 mediumtextcontent mediumtext,-- 用户 iduserid 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=utf8username: rootpassword: 1234driver-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 {// 注入拦截器@Autowiredprivate LoginIntercept loginIntercept;@Overridepublic 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 表示未登录 , 跳转到登录页面*/@Overridepublic 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 passwordif (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/adddata:{必填"username" : xxx,"password" : xxx,非必填"address" : xxx,......}}
]响应 : [{data:{"status" : 1 / -1 (注册成功 / 注册失败)"message" : "","data" : true / false}}
]
图一 :
图二 :
图三 :
图四 :
附上代码 :
/*** 2. 注册功能*/@RequestMapping(value = "/add")@Transactionalpublic 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. 注入拦截器@Autowiredprivate LoginIntercept loginIntercept;@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {registry.addResourceHandler("/product/**").addResourceLocations("file:D:/ret/");}@Overridepublic 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/**");}@Beanpublic 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=2let 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=xurl: "/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 注销功能
图一 :
图二 :
到此 这个 小项目就完成了, 其实这个项目还有很多东西可以加 ,这些大家都可以 自由发挥 .
相关文章:

博客项目
文章目录1. 创建项目2. 数据库设计3. 前置任务3.1 拦截器3.2 统一数据格式3.3 创建一个 Constant3.4 统一异常处理3.5 密码加密4. 功能实现4.1 登录功能4.2 注册功能4.3 博客列表页 (功能实现)4.3.1 左侧框4.3.2 右侧框 (分页功能 页面显示)4.4 博客详情…...

C++基础了解-20-C++类 对象
C 类 & 对象 一、C 类 & 对象 C 在 C 语言的基础上增加了面向对象编程,C 支持面向对象程序设计。类是 C 的核心特性,通常被称为用户定义的类型。 类用于指定对象的形式,它包含了数据表示法和用于处理数据的方法。类中的数据和方法…...

多态与虚(函数)表
前言续接上回(继承),我们了解了继承是如何通过虚基表,来解决派生类和父类有相同的成员变量的情况,但是类和对象中可不只有成员变量,如果成员函数也有同名,更或者如果我们想在访问不同情况&#…...

云舟案例︱视频孪生技术赋能城市安全综合管理场景,提升城市数智化水平
随着城市化发展进程的加快,人口不断膨胀,社会安全隐患等问题日益突出,成为困扰城市建设与管理的重要难题。针对各类社会治安突出问题,城市管理部门积极推进城市信息化建设,视频监控等各类信息化采集手段为城市数字化管…...

OpenFOAM 自定义gcc和openmpi安装
Compile Logs 编译很多次了但是好久不编还是会容易遗漏细节步骤,谨以为记。 使用自己编译的编译器和openmpi而不是系统自带的 库来完成对OF 的编译 依赖包安装 编译 GCC 编译 openmpi 编译OF 1依赖包安装 先 sudo apt-get install update当然,然后安…...

2023年3.8女神节买台灯怎么挑选?台灯用什么样的比较好
最近女神节,祝广大女性朋友们节日快乐啊。娱乐之余,一些实用的东西也是非常适合作为礼物送给女性朋友哦,台灯就是其中一个不错的选择。 台灯作为一种智能家居产品,不仅可见点缀卧室房间装饰,晚上的时候开启小范围照明&…...

近亿美元:人工心脏龙头永仁心医疗完成超大额A轮融资
近日,永仁心医疗器械有限公司(以下简称“永仁心医疗”)完成近一亿美元A轮融资,由北京科兴中维生物技术有限公司(SINOVAC科兴)领投,太平(深圳)医疗健康产业私募股权投资基…...

群智能优化计算中的混沌映射
经实验证明,采用混沌映射产生随机数的适应度函数值有明显提高,用混沌映射取代常规的均匀分布的随机数发生器可以得到更好的结果,特别是搜索空间中有许多局部解时,更容易搜索到全局最优解,利用混沌序列进行种群初始化、…...

【LeetCode】剑指 Offer 25. 合并两个排序的链表 p145 -- Java Version
题目链接:https://leetcode.cn/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/ 1. 题目介绍(25. 合并两个排序的链表) 输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。 【测试用例】…...

如何应对危害机房安全的这几个常见要素?
随着现代化进程的推进,各行业对计算机的依赖性日益增高,计算机系统已经成为业务系统的重要组成部分。 在这种情况下,一旦机房设备出现故障,就会影响机房的正常运行,造成严重后果。尤其是银行、证券、海关等需要实时数据…...

【bug】antd全局的主题色样式被覆盖,被修改为`antd`默认的主题色
背景: 项目本身修改了主题色,配置如下: // umi配置文件 export default {theme: {primary-color: #2F54EB, // 全局主色}, };需要对图片上传组件做封装,并在项目中统一引用,如下 import { TdsUpload } from tdsComponents;环境信息 node tiandstiandsdeMacBook…...

MySQL DDL表操作【入门到精通】
目录 一、查询创建 1、查询当前数据库所有表 2、查看指定表结构 3、查询指定表的建表语句 4、创建表结构 二、数据类型 1、数值类型 2、字符串类型 3、日期时间类型 三、表操作-案例 设计一张员工信息表,要求如下: 对应的建表语句如下&#…...

《MySQL系列-InnoDB引擎28》表-约束详细介绍
约束 1 数据完整性 关系型数据库系统和文件系统的一个不同点是,关系数据库本身能保证存储数据的完整性,不需要应用程序的控制,而文件系统一般需要在程序端进行控制。当前几乎所有的关系型数据库都提供约束(constraint)机制,该机制…...

使用docker部署宝塔环境
经常需要部署lnmp环境,宝塔是一个不错的选择,包括安装各种插件,添加网站,设置定时任务等都非常方便。这次使用docker来部署。 拉取centos镜像 docker pull centos启动容器 1.-p端口映射,-d后台运行 2. 文件夹做一下映…...

ORB_SLAM2+kinect稠密建图
下载代码:https://github.com/gaoxiang12/ORBSLAM2_with_pointcloud_map 运行代码: 解压代码后,删掉作者自己编译的build文件夹(下面三个都删除): ~/ORB_SLAM2_modified/build, ~/ORB_SLAM2_modified/T…...

mujoco安装及urdf转xml方法记录
参考 mujoco210及mujoco-py安装 下载适用于Linux或 OSX的 MuJoCo 2.1 版二进制文件 。 将mujoco210的下载的目录解压到~/.mujoco/mujoco210路径下. 注意:如果要为包指定非标准位置,请使用环境变量MUJOCO_PY_MUJOCO_PATH。 验证是否安装成功(…...

Visual Studio 2019 + Qt 项目版本信息新增到资源以及通过代码读取资源存储的版本信息
文章目录前言一、如何在VisualStudio2019中新增项目版本信息二、在程序中调用项目版本信息1.引入库version.lib1.1.通过vs自带的属性页引入库1.2.手动引入库2.新增版本信息读取类3.调用类获取信息总结前言 本文主要讲述如何在Visual Studio 2019 以及Qt结合的开发项目中&#…...

裸辞两个月还能不能找到工作?亲身经历告诉你结果·····
这是我在某论坛看到的一名网友的吐槽: 软件测试四年,主要是手动测试(部分自动化测试和性能测试,但是用的是公司内部自动化工具,而且我自动化方面是弱项。)现在裸辞两个月了,面试机会少而且面试…...

2023华为面试真题
【华为】面试真题: 面试前需要准备: 1. Java 八股文:了解常考的题型和回答思路; 2. 算法:刷 100-200 道题,记住刷题最重要的是要理解其思想,不要死记硬背,碰上原题很难࿰…...

【C++】C++11新特性——基础特性
文章目录一、列表初始化1.1 {}初始化1.2 initializer_list类型二、类型推导2.1 auto2.2 auto注意事项2.3 decltype三、新增与改进3.1 nullptr3.2 范围for3.3 array3.4 forward_list3.5 unordered系列3.6 final与override一、列表初始化 1.1 {}初始化 C11 引入了一个新的初始化…...

Mac 遇到pip: command not found问题的解决
Mac 遇到pip: command not found问题的解决在学习Playwright时候,需要下载相关依赖Playwright 是专门为满足端到端测试的需要而创建的。Playwright 支持所有现代渲染引擎,包括 Chromium、WebKit 和 Firefox。在 Windows、Linux 和 macOS 上进行本地测试或…...

[ 云计算 | Azure ] Episode 03 | 描述云计算运营中的 CapEx 与 OpEx,如何区分 CapEx 与 OpEx
正常情况如果你不是会计,或者对钱相关的数字比较敏感的财务,本文的一些东西你不会接触的,但是最为云架构或者云运营,你可能会遇到如何采购亦或者估算的我成本和运营成本等等,所以本文的一些知识点就需要进行一定的了解…...

STM32F103R8T6 SPWM实现正弦波输出
前言 PWM合成正弦波,原理什么的不详细说了,概括一下就是 PWM有效面积的积分 正弦波的有效面积。PWM的频率越快,细分的越多,锯齿也就越不明显。 做法是:首先利用正弦波取点软件,取点1000个,生…...

Oracle 11g创建和删除数据库实例
一、创建数据库实例 1.点击“开始” -> “Oracle -OraDb11g_home1” -> “Database Configuration Assistant” 2.点击“下一步” 3.选择“创建数据库”,点击“下一步” 4.默认设置,不用更改,直接点击“下一步” 5.填写要创建的“实例…...

MySQL(四)视图、存储过程、触发器
视图、存储过程、触发器视图检查选项视图的更新存储过程存储过程基本语法变量系统变量用户自定义变量局部变量if判断参数casewhile循环repeat循环loop循环cursor游标handler条件处理程序存储函数触发器视图 视图(View)是一种虚拟存在的表。视图中的数据…...

在 Ubuntu 下编写 C++
在 Ubuntu 下编写 C 在 Ubuntu 上面编写 C,本章节内容主要介绍在 Ubuntu 在终端窗口下使用 vi/vim 编辑一 个 C源文件。通过编写最简单的示例“Hello,World!”。带领大家学习如何在 Ubuntu 终端下编 辑和编译 C。这里要求大家会在 Ubuntu 上使用 vi/vim…...

Linux主要目录的意思
Linux目录的意思 文章目录Linux目录的意思bin目录(命令目录):二进制目录,二进制是可以直接执行的机器码,里面存放着可以执行的命令;bin目录右下角有个箭头类似于Windows的快捷方式 sbin目录:系…...

启动golang项目编译的exe可执行文件获取windows管理员权限(UAC)
背景: go代码启动以后里面涉及到修改ip地址等操作,需要管理员权限。打包好的exe文件双击执行默认是没有管理员权限的,那么修改ip就会提示需要管理员权限。 解决方法1:右键以管理员权限运行exe文件 解决方法2:编译exe…...

Springboot怎么快速集成Redis?
前言其实在Springboot中集成redis是一个非常简单的事情,但是为什么要单独输出一篇文章来记录这个过程呢?第一个原因是,我记性不是太好,这次把这个过程记录下,在新的项目搭建的时候或者需要在本地集成redis做一些其他相…...

COM技术简单介绍
COM (Component Object Model) 是一种面向对象的编程技术,它在 Windows 操作系统中广泛使用。COM 提供了一种标准的方法来创建和使用可重用的软件组件,这些组件可以通过不同的编程语言和应用程序进行访问和使用。 COM 技术的主要特点包括: 组…...