# 博客系统_Spring
**Repository Path**: wangzhi430/blog-system--spring
## Basic Information
- **Project Name**: 博客系统_Spring
- **Description**: 基于 Spring Boot + Spring MVC + MyBatis
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 2
- **Forks**: 0
- **Created**: 2022-06-20
- **Last Updated**: 2022-12-02
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
@[TOC]
# 1. 项目设计
**前端**使用 `HTML+CSS+JavaScript+JQuery`
**后端**使用 `Spring MVC+Spring Boot+MyBatis`

# 2. 效果展示




# 3. 创建项目并配置文件
## 1.1 创建 Spring 项目




## 1.2 配置文件
`application.properties` 配置内容
```xml
spring.profiles.active=dev
```
`application-dev.properties` 配置内容
```xml
spring.datasource.url=jdbc:mysql://localhost:3306/MyBlogSystem?characterEncoding=utf8&useSSL=true
spring.datasource.username=root
spring.datasource.password=0000
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
mybatis.mapper-locations=classpath:mapper/**Mapper.xml
```
# 4. 数据库实现用户和博客管理
## 4.1 设计数据库
这里博客系统, 是一个用户表和博客表,
> 用户一般分为:
> - 用户Id (每个人一个且互不相同)
> - 用户名 (每个人的用户名不相同)
> - 密码
> - 用户头像(暂不实现)
> - 用户马云地址(暂不实现)
> 博客一般分为:
> - 博客Id (每篇博客一个且互不相同)
> - 标题
> - 正文
> - 创建时间
> - 修改时间 (这里没用到, 自己想加也可以)
> - 用户Id (可以设置一个外键,这里没设置)
```sql
create database if not exists MyBlogSystem;
use MyBlogSystem;
drop table if exists blog;
-- 创建一个博客表
create table blog (
blogId int primary key auto_increment,
title varchar(1024),
content mediumtext,
postTime datetime,
userId int
);
drop table if exists user;
-- 创建一个用户信息表
create table user (
userId int primary key auto_increment,
username varchar(128) unique,
password varchar(128)
);
```
## 4.2 使用 MyBatis 操作数据库
> 在 `resources` 下创建一个 `mapper` 包. 包下创建 `UserMapper.xml` 和
> `BlogMapper.xml`
> - BlogMapper.xml 里是对博客表的数据库操作
> - UserMapper.xml 里是对用户表的数据库操作
### UserMapper.xml
```xml
insert into user(username password) values (#{username}, #{password})
```
### BlogMapper.xml
```xml
insert into blog(title,content,postTime,userId) values(#{title},#{content},#{postTime},#{userId})
delete from blog where blogId = #{blogId}
update blog set content = #{content},title = #{title} where blogId = #{blogId}
```
### User 实体类 和 Blog 实体类
在 model 包下 创建 User 类 和 Blog 类
User 类
```java
@Data
public class User {
public int userId;
public String username;
public String password;
}
```
Blog 类
```java
@Data
public class Blog {
public int blogId;
public String title;
public String content;
public Timestamp postTime;
public int userId;
}
```
### UserMapper 接口 和 BlogMapper 接口
在 mapper 包下创建 UserMapper 和 BlogMapper 接口
UserMapper
```java
@Mapper
public interface UserMapper {
void addUser(User user);
User selectByName(String username);
User selectById(Integer userId);
}
```
BlogMapper
```java
@Mapper
public interface BlogMapper {
List getAllBlog();
Blog getBlogByBid(Integer blogId);
void postBlog(Blog blog);
void deleteBlog(Integer blogId);
void updateBlog(Blog blog);
List getAllBlogById();
}
```
### UserService 类 和 BlogService 类
`service` 包下创建 **UserService类** 和 **BlogService类**
`UserService`
```java
@Service
public class UserService {
@Resource
private UserMapper userMapper;
public void addUser(User user){
userMapper.addUser(user);
}
public User selectByName(String username){
return userMapper.selectByName(username);
}
public User selectById(Integer userId){
return userMapper.selectById(userId);
}
}
```
`BlogService`
```java
@Service
public class BlogService {
@Resource
private BlogMapper blogMapper;
public List getAllBlog(){
return blogMapper.getAllBlog();
}
public Blog getBlogByBid(Integer blogId){
return blogMapper.getBlogByBid(blogId);
}
public void postBlog(Blog blog) {
blogMapper.postBlog(blog);
}
public void deleteBlog(Integer blogId){
blogMapper.deleteBlog(blogId);
}
public void updateBlog(Blog blog){
blogMapper.updateBlog(blog);
}
public List getAllBlogById(){
return blogMapper.getAllBlogById();
}
}
```
# 5. 前后端交互接口设计

**交互1**

**交互2**

**交互3**

**交互4**

**交互5**

**交互6**

**交互7**

**交互8**

**交互9**

**交互10**

**交互11**

# 6. 导入前端代码
**导入前端代码到 `resources` 的 `static`下**

具体代码查看 文章 [博客系统前端界面](https://wangzhi430.blog.csdn.net/article/details/124649884)
[https://wangzhi430.blog.csdn.net/article/details/124649884](https://wangzhi430.blog.csdn.net/article/details/124649884)
# 7. 实现博客主页
这里的交互接口是 `交互6`
## 7.1 实现后端代码
```java
@RestController
public class IndexController {
@Autowired
private BlogService blogService;
@RequestMapping("/index")
public List getAllBlog() {
return blogService.getAllBlog();
}
}
```
## 7.2 实现前端代码
```javascript
$.ajax({
url: "index",
method: "GET",
success: function(data,status) {
buildBlogs(data);
}
})
function buildBlogs(blogs){
let rightDiv = document.querySelector('.right');
for(let blog of blogs){
let blogDiv = document.createElement('div');
blogDiv.className = 'article';
// 创建 title
let h2 = document.createElement('h2');
h2.className = 'title';
h2.innerHTML = blog.title;
blogDiv.appendChild(h2);
// 创建 postTime
let postTime = document.createElement('span');
postTime.className = 'date';
postTime.innerHTML = DateFormat(blog.postTime);
blogDiv.appendChild(postTime);
// 创建 content
let content = document.createElement('div');
content.className = 'desc';
content.innerHTML = blog.content;
blogDiv.appendChild(content);
// 创建 详情页的超链接
let detailA = document.createElement('a');
detailA.className = 'more';
detailA.href = 'art.html?blogId=' + blog.blogId;
detailA.innerHTML = '查看全文>>';
blogDiv.appendChild(detailA);
// 加入到 right 中
rightDiv.appendChild(blogDiv);
}
}
// 把毫秒级时间戳转化成格式化日期
function DateFormat(timeStampMS) {
var date = new Date(timeStampMS);
var year = date.getFullYear(),
month = date.getMonth()+1,//月份是从0开始的
day = date.getDate(),
hour = date.getHours(),
min = date.getMinutes(),
sec = date.getSeconds();
var newTime = year + '-' +
(month < 10? '0' + month : month) + '-' +
(day < 10? '0' + day : day) + ' ' +
(hour < 10? '0' + hour : hour) + ':' +
(min < 10? '0' + min : min) + ':' +
(sec < 10? '0' + sec : sec);
return newTime;
}
```
## 7.3 测试代码

## 7.4 解决页面内容太多超出当前浏览器


## 7.5 解决页面顺序不是按最新时间排序

**在BlogMapper.xml中修改当前sql语句.**

## 7.6 解决内容太多, 导致显示的时候占位太多.


## 7.7 再次测试代码

# 8. 实现博客详情页
这里的交互是 `交互5`
## 8.1 实现后端代码
根据当前blogId来获取文章, 要判断blogId是否合法, 是否存在当前blogId的文章
这里使用, message来判断, 返回的message不为空就表示异常.
```java
@RestController
public class DetailsController {
@Autowired
private BlogService blogService;
@RequestMapping("/details")
public Object ShowBlog(Integer blogId) {
HashMap map = new HashMap<>();
if (blogId == null || blogId < 1) {
map.put("message","blogId异常!");
return map;
}
Blog blog = blogService.getBlogByBid(blogId);
if (blog == null) {
map.put("message","不存在当前blogId的文章");
return map;
}
return blog;
}
}
```
## 8.2 实现前端代码
```javascript
$.ajax({
url: "details"+location.search,
method: "GET",
success: function(data,status) {
if(data.message == null) {
buildBlog(data);
} else {
alert(data.message);
location.assign("home.html");
}
}
})
function buildBlog(blog){
// 1. 更新 title
let titleDiv = document.querySelector('.title');
titleDiv.innerHTML = blog.title;
// 2. 更新 postTime
let postTime = document.querySelector('.date');
postTime.innerHTML = DateFormat(blog.postTime);
editormd.markdownToHTML('content', {markdown: blog.content});
}
// 把毫秒级时间戳转化成格式化日期
function DateFormat(timeStampMS) {
var date = new Date(timeStampMS);
var year = date.getFullYear(),
month = date.getMonth()+1,//月份是从0开始的
day = date.getDate(),
hour = date.getHours(),
min = date.getMinutes(),
sec = date.getSeconds();
var newTime = year + '-' +
(month < 10? '0' + month : month) + '-' +
(day < 10? '0' + day : day) + ' ' +
(hour < 10? '0' + hour : hour) + ':' +
(min < 10? '0' + min : min) + ':' +
(sec < 10? '0' + sec : sec);
return newTime;
}
```
## 8.3 测试代码

## 8.4 这里展示为markdown语法的正文
展示为 markdown 语法的正文.
注意这里的几段代码


这里还需要导入依赖包

# 9. 实现博客登录界面
这里的交互是 `交互8`
## 9.1 实现后端代码
这里要根据前端穿过来的 json 格式数据进行判断
判断当前是否存在用户, 以及当前用户密码是否正确
登录成功之后, 要创建一个session
```java
@RestController
public class LoginController {
@Autowired
private UserService userService;
@RequestMapping("/login")
public Object userLogin(@RequestBody User user, HttpServletRequest request) {
HashMap map = new HashMap<>();
if (user == null) {
map.put("message","当前还没有输入用户名和密码,登录失败!");
return map;
}
User user1 = userService.selectByName(user.getUsername());
if (user1 == null) {
map.put("message","当前用户名不存在!");
return map;
}
if (!user.getPassword().equals(user1.getPassword())) {
map.put("message","当前用户名密码错误!");
return map;
}
user.setUserId(user1.getUserId());
HttpSession session = request.getSession(true);
if (session != null) {
session.setAttribute("user",user);
}
return map;
}
}
```
## 9.2 实现前端代码
这里前端去除了前后空格,以及为空的情况
```java
let submit = document.querySelector('.button');
submit.onclick = function() {
let username = document.querySelector('.user');
let password = document.querySelector('.password');
if (username.value.trim() == ""){
alert('请先输入用户名!');
username.focus();
return;
}
if (password.value.trim() == ""){
alert('请先输入密码!');
password.focus();
return;
}
$.ajax({
url: "login",
method: "POST",
data: JSON.stringify({username: username.value.trim(), password: password.value.trim()}),
contentType: "application/json;charset=utf-8",
success: function(data, status) {
if(data.message == null) {
location.assign("home.html");
}else{
alert(data.message);
username.value="";
password.value="";
username.focus();
}
}
})
}
```
# 10. 实现登录判断 --- 拦截器
### 10.1 实现自定义拦截器
```java
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("user") != null) {
return true;
}
response.setStatus(401);
response.sendRedirect("/login.html");
return false;
}
}
```
## 10.2 将自定义拦截器加入到系统配置
```java
@Configuration
public class AppConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/**/*.js")
.excludePathPatterns("/**/*.jpg")
.excludePathPatterns("/**/*.css")
.excludePathPatterns("/**/*.png")
.excludePathPatterns("/**/login.html")
.excludePathPatterns("/**/register.html")
.excludePathPatterns("/**/login")
.excludePathPatterns("/**/register");
}
}
```
# 11. 实现注册功能
这里的交互是 `交互9`
## 11.1 实现后端代码
实现一个 类 Register 来接收前端返回来的数据
```java
class Register {
public String username;
public String password1;
public String password2;
}
```
这里后端需要判断当前用户名是否已经被使用.
```java
@RestController
public class RegisterController {
@Autowired
private UserService userService;
@RequestMapping("/register")
public Object userRegister(@RequestBody Register register) {
HashMap map = new HashMap<>();
User user = userService.selectByName(register.username);
if (user != null) {
map.put("message","当前用户名已经存在了, 请更换!");
return map;
}
User user1 = new User();
user1.setUsername(register.username);
user1.setPassword(register.password1);
userService.addUser(user1);
return map;
}
}
```
## 11.2 实现前端代码
要对输入内容去除前后空格,并且判空
```javascript
let submit = document.querySelector('.button');
submit.onclick = function() {
let username = document.querySelector('.user');
let password1 = document.querySelector('.password1');
let password2 = document.querySelector('.password2');
if(username.value.trim() == ""){
alert("请先输入用户名!");
username.focus();
return;
}
if(password1.value.trim() == ""){
alert('请先输入密码!');
password1.focus();
return;
}
if(password2.value.trim() == ""){
alert('请再次输入密码!');
password2.focus();
return;
}
if(password1.value.trim() != password2.value.trim()) {
alert('两次输入的密码不同!');
passwrod1.value="";
password2.value="";
return;
}
$.ajax({
url: "register",
method: "POST",
data: JSON.stringify({username: username.value.trim(), password1: password1.value.trim(),password2: password2.value.trim()}),
contentType: "application/json;charset=utf-8",
success: function(data,status){
if(data.message != null){
alert(data.message);
username.value="";
password1.value="";
password2.value="";
username.focus();
}else{
location.assign('login.html');
}
}
})
}
```
# 12. 实现注销功能
这里的交互是 `交互10`
## 12.1 实现后端代码
因为 注销功能是点击注销的时候, 触发一个logout的url, 然后发送一个请求.
这里只需要实现后端代码既可
```java
@Controller
public class LogoutController {
@RequestMapping("/logout")
public void userLogout(HttpServletRequest request, HttpServletResponse response) throws IOException {
HttpSession session = request.getSession(false);
// 拦截器的拦截, 所以不可能出现session为空的情况
session.removeAttribute("user");
response.sendRedirect("login.html");
}
}
```
# 13. 实现博客编辑页
这里的交互是 `交互1`
## 13.1 实现后端代码
```java
@RestController
public class EditController {
@Autowired
private BlogService blogService;
@RequestMapping("/edit")
public void postBlog(@RequestBody Blog blog, @SessionAttribute(value = "user",required = false)User user){
blog.setPostTime(new Timestamp(System.currentTimeMillis()));
blog.setUserId(user.getUserId());
blogService.postBlog(blog);
}
}
```
## 13.2 实现前端代码
```java
let submit = document.querySelector('.publish');
submit.onclick = function() {
let title = document.querySelector('.title');
let content = document.querySelector('.content');
if(title.value.trim() == ""){
alert('当前文章标题为空,请输入!');
title.focus();
return;
}
if(content.value.trim() == ""){
alert('当前文章内容为空,请输入!');
content.focus();
return;
}
$.ajax({
url: "edit",
method: "POST",
data: JSON.stringify({title: title.value.trim(), content: content.value.trim()}),
contentType: "application/json;charset=utf-8",
success: function(data,status) {
location.assign('home.html');
}
})
}
```
# 14. 实现博客个人主页
这里的交互是 `交互7`
这里的前端页面主要就是主页页面的改进
## 14.1 实现后端代码
```java
@RestController
public class PersonController {
@Autowired
private BlogService blogService;
@RequestMapping("/person")
public List getMyBlog(@SessionAttribute(value = "user",required = false)User user) {
List blogs = blogService.getAllBlogById(user.getUserId());
for (Blog blog : blogs) {
if (blog.getContent().length() > 80) {
blog.setContent(blog.getContent().substring(0,80) + " ...");
}
}
return blogs;
}
}
```
## 14.2 实现前端代码
```javascript
$.ajax({
url: "person",
method: "GET",
success: function(data,status) {
buildBlogs(data);
}
})
function buildBlogs(blogs){
let rightDiv = document.querySelector('.right');
for(let blog of blogs){
let blogDiv = document.createElement('div');
blogDiv.className = 'article';
// 创建 title
let h2 = document.createElement('h2');
h2.className = 'title';
h2.innerHTML = blog.title;
blogDiv.appendChild(h2);
// 创建 postTime
let postTime = document.createElement('span');
postTime.className = 'date';
postTime.innerHTML = DateFormat(blog.postTime);
blogDiv.appendChild(postTime);
// 创建 content
let content = document.createElement('div');
content.className = 'desc';
content.innerHTML = blog.content;
blogDiv.appendChild(content);
// 创建 详情页的超链接
let detailA = document.createElement('a');
detailA.className = 'more';
detailA.href = 'art.html?blogId=' + blog.blogId;
detailA.innerHTML = '查看全文>>';
blogDiv.appendChild(detailA);
// 加入到 right 中
rightDiv.appendChild(blogDiv);
}
}
// 把毫秒级时间戳转化成格式化日期
function DateFormat(timeStampMS) {
var date = new Date(timeStampMS);
var year = date.getFullYear(),
month = date.getMonth()+1,//月份是从0开始的
day = date.getDate(),
hour = date.getHours(),
min = date.getMinutes(),
sec = date.getSeconds();
var newTime = year + '-' +
(month < 10? '0' + month : month) + '-' +
(day < 10? '0' + day : day) + ' ' +
(hour < 10? '0' + hour : hour) + ':' +
(min < 10? '0' + min : min) + ':' +
(sec < 10? '0' + sec : sec);
return newTime;
}
```
# 15. 实现展示用户信息的功能
这里的交互是 `交互11`
这里需要分情况考虑, 展示个人信息主要是 主页页面, 详情页面, 个人主页页面.
以带不带blogId来区分
## 15.1 实现后端代码
这里判断了 blogId丢失的情况以及,文章作者丢失情况(数据库表数据被删除的时候会出现这种错误)
```java
@RestController
public class UserController {
@Autowired
private UserService userService;
@Autowired
private BlogService blogService;
@RequestMapping("/user")
public Object getUser(Integer blogId, @SessionAttribute(value = "user",required = false)User user){
if(blogId == null) {
return user;
}else {
HashMap map = new HashMap<>();
Blog blog = blogService.getBlogByBid(blogId);
if(blog == null) {
map.put("message","不存在当前blogId的文章");
return map;
}
User author = userService.selectById(blog.getUserId());
if(author == null){
map.put("message","当前文章作者出错");
return map;
}
return author;
}
}
}
```
## 15.2 实现前端代码
详情页的情况:
```javascript
$.ajax({
url: "user"+location.search,
method: "GET",
success: function(data,status) {
if(data.message == null){
let username = document.querySelector('.name');
username.innerHTML = data.username;
}else{
alert(data.message);
location.assign('home.html');
}
}
})
```
个人主页和主页的情况
```javascript
$.ajax({
url: "user",
method: "GET",
success: function(data,status){
let username = document.querySelector('.name');
username.innerHTML = data.username;
}
})
```
# 16. 实现博客的删除功能
这里需要用到 `交互2`
这里在详情页的时候进行构建, 在Blog实体类中加一项 `isAuthor`, 为1的时候就是当前文章就是作者.
前端接收到这个的时候, 进行判断, 如果为1就显示删除的按钮.
## 16.1 改进代码




## 16.2 实现后端代码
```java
@Controller
public class DeleteController {
@Autowired
private BlogService blogService;
@RequestMapping("/delete")
public Object deleteBlog(Integer blogId) {
blogService.deleteBlog(blogId);
return "/home.html";
}
}
```
# 17. 实现博客的修改功能
这里的交互是 `交互3` 和 `交互4`
交互3是在新的页面进行加载
## 17.1 实现后端代码
```java
@RestController
public class UpdateController {
@Autowired
private BlogService blogService;
@RequestMapping("/updateLoad")
public Object updateLoad(Integer blogId){
HashMap map = new HashMap<>();
if(blogId == null) {
map.put("message","blogId丢失!");
return map;
}
Blog blog = blogService.getBlogByBid(blogId);
if(blog == null) {
map.put("blog","不存在当前blog的文章!");
return map;
}
return blog;
}
@RequestMapping("/update")
public Object Update(Integer blogId, @RequestBody Blog blog, @SessionAttribute(value = "user",required = false)User user) {
HashMap map = new HashMap<>();
if(blogId == null) {
map.put("message","blogId丢失!");
return map;
}
blog.setBlogId(blogId);
blog.setUserId(user.getUserId());
blogService.updateBlog(blog);
return map;
}
}
```
## 17.2 实现前端代码
```javascript
$.ajax({
url: "updateLoad"+location.search,
method: "GET",
success: function(data,status) {
if(data.message == null) {
let title = document.querySelector('.title');
title.value=data.title;
let content = document.querySelector('.content');
content.value=data.content;
}else{
alert(data.message);
location.assign('home.html');
}
}
})
// 初始化编辑器
var editor = editormd("editor", {
// 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉.
width: "100%",
// 高度 100% 意思是和父元素一样高. 要在父元素的基础上去掉标题编辑区的高度
height: "calc(100% - 60px)",
// 指定 editor.md 依赖的插件路径
path: "editor.md/lib/",
// 放到 textarea中
saveHTMLToTextArea: true
});
let submit = document.querySelector('.publish');
submit.onclick = function() {
let title = document.querySelector('.title');
let content = document.querySelector('.content');
if(title.value.trim() == ""){
alert('当前文章标题为空,请输入!');
title.focus();
return;
}
if(content.value.trim() == ""){
alert('当前文章内容为空,请输入!');
content.focus();
return;
}
$.ajax({
url: "update"+location.search,
method: "POST",
data: JSON.stringify({title: title.value.trim(), content: content.value.trim()}),
contentType: "application/json;charset=utf-8",
success: function(data,status) {
if(data.message == null){
location.assign('home.html');
}else{
alert(data.message);
location.assign('home.html');
}
}
})
}
```