• 基于SpringBoot+MyBatis实现的个人博客系统(一)


    这篇主要讲解一下如何基于SpringBoot和MyBatis技术实现一个简易的博客系统(前端页面主要是利用CSS,HTML进行布局书写),前端的静态页面代码可以直接复制粘贴,后端的接口以及前端发送的Ajax请求需要自己书写.

    博客系统需要完成的接口:

    • 注册
    • 登录
    • 博客列表页展示
    • 博客详情页展示
    • 发布博客
    • 修改博客
    • .......

     完整版代码详见Gitee:blogsystem · 徐明园/SSM配置信息 - 码云 - 开源中国 (gitee.com)

    项目亮点:

    1. 密码实现加盐处理,确保安全性;
    2. Session升级,由原来的内存存储改为通过Redis存储,不会丢失,并且支持分布式部署;
    3. 功能升级,对于博客列表的展示添加了分页功能;
    4. 登录验证升级,添加了拦截器的功能对用于的登录进行校验;
    5. ......

    一, 项目的搭建

    1,导入依赖坐标(创建SpringBoot项目)

    在书写任何一个项目的同时,需要先将项目的基础给搭建好,搭建项目需要提前考虑好项目的一些功能需要哪些依赖,从而进行添加,如何创建SpringBoot项目可以看我的另一篇博客:SpringBoot项目的创建和使用_蜡笔小心眼子!的博客-CSDN博客

     博客系统需要添加的依赖如下:

    1. "1.0" encoding="UTF-8"?>
    2. "http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    4. 4.0.0
    5. org.springframework.boot
    6. spring-boot-starter-parent
    7. 2.7.16
    8. com.example
    9. blogsystem
    10. 0.0.1-SNAPSHOT
    11. blogsystem
    12. blogsystem
    13. 1.8
    14. org.springframework.boot
    15. spring-boot-starter-web
    16. org.mybatis.spring.boot
    17. mybatis-spring-boot-starter
    18. 2.3.1
    19. org.springframework.boot
    20. spring-boot-devtools
    21. runtime
    22. true
    23. com.mysql
    24. mysql-connector-j
    25. runtime
    26. org.projectlombok
    27. lombok
    28. true
    29. org.springframework.boot
    30. spring-boot-starter-test
    31. test
    32. org.mybatis.spring.boot
    33. mybatis-spring-boot-starter-test
    34. 2.3.1
    35. test
    36. org.springframework.boot
    37. spring-boot-maven-plugin
    38. org.projectlombok
    39. lombok

    2,书写配置文件

    需要使用MyBatis技术的项目需要配置你的数据库相关的信息以及Mapper的xml文件存储的位置,同时也可以在配置文件中定义一下日志的打印级别,从而方便查看数据库操作的完整信息:

    1. #配置数据库连接信息
    2. spring:
    3. datasource:
    4. url: "你自己的数据库"
    5. username: root
    6. password: "数据库对应的密码"
    7. driver-class-name: com.mysql.cj.jdbc.Driver
    8. #配置Mapper的xml文件存储信息
    9. mybatis:
    10. # xml的存储位置
    11. mapper-locations: classpath:mapper/*Mapper.xml
    12. configuration:
    13. log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    14. #配置日志打印级别
    15. logging:
    16. level:
    17. com:
    18. example:
    19. demo: debug

    二, 将前端页面部署到项目中(resource文件下的static目录中)

    对于后端程序员来说可以不用特别注重前端样式的书写,但是需要看得懂前端的代码以及和后端交互的请求即可(学有余力的情况下可以适当学习从而优化自己项目中的前端页面),这里的静态页面信息可以直接在我的码云中进行下载:SSM配置信息: 存放SSM项目中的一些配置信息 (gitee.com)博客系统(静态页面).zip · 徐明园/SSM配置信息 - 码云 - 开源中国 (gitee.com)SSM配置信息: 存放SSM项目中的一些配置信息 (gitee.com)

    将下载好的前端页面全选之后直接复制到static目录下即可:

    三, 初始化数据库

    在配置文件中我们已经配置了数据库的连接信息,但是此时在数据库中还没有初始化一些数据,所以我们需要初始化数据,方便写项目的时候进行测试,初始化数据库的SQL代码也可以在我们的码云中进行下载:博客系统初始化数据库-ssm.sql · 徐明园/SSM配置信息 - 码云 - 开源中国 (gitee.com)

    查看数据库中的表即相应表结构:

     

     四, 对项目的整体架构进行分层

    一个企业级的SM项目都需要对其进行合理的分层,每一层处理每一层的业务逻辑,我在项目中的分层如下:

    • common:一些工具类
    • config:配置信息类
    • controller:处理前端请求的类
    • entity:实体类(也可以写成model)
    • mapper:用来和Mapper.xml文件交互的接口类
    • service:处于controller和mapper之间的类

    五, 书写前后端交互的功能

    从这里开始就是项目的核心了,这里开始可以对前后端的接口和相应功能进行书写了!

    1, 统一返回对象的封装

    为了给前端返回统一的对象,后端需要定义一个类对返回的数据进行封装,该类包含code,msg和data三个属性,该类定义在common包下:

    1. package com.example.blogsystem.common;
    2. import lombok.Data;
    3. import java.io.Serializable;
    4. /**
    5. * 统一返回对象
    6. * 返回成功的话 code 设置成 200
    7. * 反悔失败的话 code 设置成本身的 code
    8. */
    9. @Data
    10. public class AjaxResult implements Serializable {
    11. private int code;
    12. private String msg;
    13. private Object data;
    14. /**
    15. * 返回成功
    16. *
    17. * @param data
    18. * @return
    19. */
    20. public static AjaxResult success(Object data) {
    21. AjaxResult ajaxResult = new AjaxResult();
    22. ajaxResult.setCode(200);
    23. ajaxResult.setMsg("");
    24. ajaxResult.setData(data);
    25. return ajaxResult;
    26. }
    27. public static AjaxResult success(Object data, String msg) {
    28. AjaxResult ajaxResult = new AjaxResult();
    29. ajaxResult.setCode(200);
    30. ajaxResult.setMsg(msg);
    31. ajaxResult.setData(data);
    32. return ajaxResult;
    33. }
    34. /**
    35. * 返回失败
    36. *
    37. * @param code
    38. * @param msg
    39. * @return
    40. */
    41. public static AjaxResult fail(Integer code, String msg) {
    42. AjaxResult ajaxResult = new AjaxResult();
    43. ajaxResult.setCode(code);
    44. ajaxResult.setMsg(msg);
    45. ajaxResult.setData("");
    46. return ajaxResult;
    47. }
    48. public static AjaxResult fail(Integer code, String msg, String data) {
    49. AjaxResult ajaxResult = new AjaxResult();
    50. ajaxResult.setCode(code);
    51. ajaxResult.setMsg(msg);
    52. ajaxResult.setData(data);
    53. return ajaxResult;
    54. }
    55. }

    2, 注册功能

    注册功能就是用户给后端发送一次请求之后,后端就会在数据中新增一条用户记录!

    前端

    1. "en">
    2. "UTF-8">
    3. "X-UA-Compatible" content="IE=edge">
    4. "viewport" content="width=device-width, initial-scale=1.0">
    5. 注册页面
    6. "stylesheet" href="css/conmmon.css">
    7. "stylesheet" href="css/login.css">
    8. "nav">
    9. "img/logo2.jpg" alt="">
    10. "title">我的博客系统
    11. "spacer">
  • "login-container">
  • "login-dialog">
  • 注册

  • "row">
  • 用户名
  • "text" id="username">
  • "row">
  • 密码
  • "password" id="password">
  • "row">
  • 确认密码
  • "password" id="password2">
  • "row">
  •  前端给后端发送请求都是通过Ajax来实现的,所以对于任何需要发送Ajax请求的页面都需要导入js的依赖(后面就不再赘述)

    后端(这里的后端只展示controller包中的代码)

    1. @RequestMapping("/reg")
    2. public AjaxResult reg(UserInfo userInfo) {
    3. //1.对前端传递来的参数进行校验
    4. if (userInfo == null || !StringUtils.hasLength(userInfo.getUsername()) || !StringUtils.hasLength(userInfo.getPassword())) {
    5. return AjaxResult.fail(-1, "参数有误!");
    6. }
    7. //2.与数据库进行交互实现注册的功能
    8. //将密码进行加盐加密
    9. userInfo.setPassword(PasswordTools.encrypt(userInfo.getPassword()));
    10. int result = userService.reg(userInfo);
    11. //3.对于查询结果给前端进行反馈
    12. return AjaxResult.success(result);
    13. }

    3, 登录功能 

    登录就是前端给后端传递用户名和密码,后端从数据库中查询是否存在这样的用户!

    前端

    1. "en">
    2. "UTF-8">
    3. "X-UA-Compatible" content="IE=edge">
    4. "viewport" content="width=device-width, initial-scale=1.0">
    5. 登陆页面
    6. "stylesheet" href="css/conmmon.css">
    7. "stylesheet" href="css/login.css">
    8. "nav">
    9. "img/logo2.jpg" alt="">
    10. "title">我的博客系统
    11. "spacer">
    12. "login-container">
    13. "login-dialog">
    14. 登陆

    15. "row">
    16. 用户名
    17. "text" id="username">
    18. "row">
    19. 密码
    20. "password" id="password">
    21. "row">

    登录功能的前端代码和注册功能前端代码几乎一模一样

    后端

    1. @RequestMapping("/login")
    2. public AjaxResult login(String username, String password, HttpServletRequest request) {
    3. //1.对前端传递来的参数进行校验
    4. if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
    5. return AjaxResult.fail(-1, "参数有误!");
    6. }
    7. //2.根据用户名去数据库中进行查询
    8. UserInfo userInfo = userService.login(username);
    9. if (userInfo == null || userInfo.getId() <= 0) {
    10. return AjaxResult.fail(-2, "用户名或者密码错误!");
    11. }
    12. //对数据库中查找的密码进行解密
    13. // if (!PasswordTools.check(password,userInfo.getPassword())) {
    14. // return AjaxResult.fail(-2, "用户名或者密码错误!");
    15. // }
    16. if (!userInfo.getPassword().equals(password)) {
    17. return AjaxResult.fail(-2, "用户名或者密码错误!");
    18. }
    19. //当前表示登陆成功 需要存储session
    20. HttpSession session = request.getSession();
    21. session.setAttribute(ApplicationVariable.USERINFO_SESSION_KEY, userInfo);
    22. return AjaxResult.success(1);
    23. }

    登录的时候需要存储用户的session(会话)信息,因为session的Key需要在多个地方使用,我们将该属性抽象出来放在了common这个公共包下了:

    1. package com.example.blogsystem.common;
    2. public class ApplicationVariable {
    3. public static final String USERINFO_SESSION_KEY = "USERINFO_SESSION_KEY";
    4. }

     对于取出session()会话)中的的用户信息也可以将其封装公共的类,放在common包下:

    1. package com.example.blogsystem.common;
    2. import com.example.blogsystem.entity.UserInfo;
    3. import javax.servlet.http.HttpServletRequest;
    4. import javax.servlet.http.HttpSession;
    5. public class UserSessionTools {
    6. public static UserInfo getLoginUser(HttpServletRequest request) {
    7. HttpSession session = request.getSession(false);
    8. if (session != null && session.getAttribute(ApplicationVariable.USERINFO_SESSION_KEY) != null) {
    9. return (UserInfo) session.getAttribute(ApplicationVariable.USERINFO_SESSION_KEY);
    10. }
    11. return null;
    12. }
    13. }

    4, 实现拦截器

    对于一些博客信息的操作需要用户进行登录,所以可以通过拦截器判断用户具有相应的权限,只有通过拦截器的用户才可以操作,可以将拦截的配置信息放在config包下.

    实现HandlerInterceptor接口:

    1. package com.example.blogsystem.config;
    2. import com.example.blogsystem.common.ApplicationVariable;
    3. import org.springframework.context.annotation.Configuration;
    4. import org.springframework.web.servlet.HandlerInterceptor;
    5. import javax.servlet.http.HttpServletRequest;
    6. import javax.servlet.http.HttpServletResponse;
    7. import javax.servlet.http.HttpSession;
    8. @Configuration
    9. public class LoginInterceptor implements HandlerInterceptor {
    10. @Override
    11. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    12. HttpSession session = request.getSession(false);
    13. if (session != null && session.getAttribute(ApplicationVariable.USERINFO_SESSION_KEY) != null) {
    14. return true;
    15. }
    16. response.sendRedirect("/login.html");//没有通过拦截器的请求需要跳转到登录页面先登录
    17. return false;
    18. }
    19. }

    实现WebMvcConfigurer进行配置接口:

    1. package com.example.blogsystem.config;
    2. import org.springframework.context.annotation.Configuration;
    3. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    4. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    5. import javax.annotation.Resource;
    6. @Configuration
    7. public class MyConfig implements WebMvcConfigurer {
    8. @Resource
    9. private LoginInterceptor loginInterceptor;
    10. @Override
    11. public void addInterceptors(InterceptorRegistry registry) {
    12. registry.addInterceptor(loginInterceptor)
    13. .addPathPatterns("/**") //拦截所有的url
    14. .excludePathPatterns("/login.html")
    15. .excludePathPatterns("/reg.html")
    16. .excludePathPatterns("/blog_list.html")
    17. .excludePathPatterns("/blog_content.html")
    18. .excludePathPatterns("/css/**")
    19. .excludePathPatterns("/editor.md/**")
    20. .excludePathPatterns("/img/**")
    21. .excludePathPatterns("/js/**")
    22. .excludePathPatterns("/user/reg")
    23. .excludePathPatterns("/user/login");
    24. }
    25. }

    这里需要先放开所有的静态页面,图片以及登录和注册接口.

    5, 博客添加功能

    博客添加功能就是前端向后端提交博客的一些信息,后端在文章表中插入一条文章记录即可!

    前端

    1. "en">
    2. "UTF-8">
    3. "X-UA-Compatible" content="IE=edge">
    4. "viewport" content="width=device-width, initial-scale=1.0">
    5. 文章添加
    6. "stylesheet" href="css/conmmon.css">
    7. "stylesheet" href="css/blog_edit.css">
    8. "stylesheet" href="editor.md/css/editormd.min.css" />
    9. "nav">
    10. "img/logo2.jpg" alt="">
    11. "title">我的博客系统
    12. "spacer">
    13. "blog-edit-container">
    14. "title">
    15. "title" type="text" placeholder="在这里写下文章标题">
    16. "editorDiv">

    这里的前端页面引入了MarkDown编辑器,所以添加博客的时候相较于其他官方博客系统更加真实,而且该编辑器提供了一些Api让我们进行格式转换的时候更加方便.

    1. //相关API
    2. alert(editor.getValue()); // 获取值
    3. editor.setValue("#123") // 设置值

    后端

    1. @RequestMapping("/add")
    2. public AjaxResult add(ArticleInfo articleInfo, HttpServletRequest request) {
    3. //1.对前端传递来的参数进行判空操作
    4. if (articleInfo == null || !StringUtils.hasLength(articleInfo.getTitle()) || !StringUtils.hasLength(articleInfo.getContent())) {
    5. return AjaxResult.fail(-1, "参数错误!");
    6. }
    7. //2.获取当前的uid进行校验
    8. UserInfo userInfo = UserSessionTools.getLoginUser(request);
    9. if (userInfo == null || userInfo.getId() <= 0) {
    10. return AjaxResult.fail(-1, "参数错误!");
    11. }
    12. //3.封装uid进行持久化
    13. articleInfo.setUid(userInfo.getId());
    14. int result = articleService.add(articleInfo);
    15. //4.给前端进行数据反馈
    16. return AjaxResult.success(result);
    17. }

    这里数据库存储的是MarkDown格式的数据,是为了方便进行修改的时候直接进行修改省去了一次从Html转换成MarkDown格式的操作.

    6, 博客编辑功能

    博客编辑功能需要实现两个操作:

    1.先去查询当前文章的信息进行展示(页面加载的时候进行调用)

    2.提交修改操作(触发提交按钮的时候进行调用)

    前端

    1. "en">
    2. "UTF-8">
    3. "X-UA-Compatible" content="IE=edge">
    4. "viewport" content="width=device-width, initial-scale=1.0">
    5. 文章修改
    6. "stylesheet" href="css/conmmon.css">
    7. "stylesheet" href="css/blog_edit.css">
    8. "stylesheet" href="editor.md/css/editormd.min.css"/>
    9. "nav">
    10. "img/logo2.jpg" alt="">
    11. "title">我的博客系统
    12. "spacer">
    13. "blog-edit-container">
    14. "title">
    15. "title" type="text" placeholder="在这里写下文章标题">
    16. "editorDiv">

    后端

    1. /**
    2. * 对文章进行修改的时候也需要对拿到文章进行权限验证 拿到的文章的uid必须和登录的用户的id一致
    3. * 防止登录的用户对其他人的文章进行篡改
    4. */
    5. @RequestMapping("/getdetailbyid")
    6. public AjaxResult getdetailbyid(Integer id, HttpServletRequest request) {
    7. //1.对id进行判空操作
    8. if (id == null || id <= 0) {
    9. return AjaxResult.fail(-1, "参数错误!");
    10. }
    11. //2.获取到登录用户的id
    12. UserInfo userInfo = UserSessionTools.getLoginUser(request);
    13. if (userInfo == null || userInfo.getId() <= 0) {
    14. return AjaxResult.fail(-1, "参数错误!");
    15. }
    16. //3.封装id和uid进行持久化操作
    17. return AjaxResult.success(articleService.getDetailByIdAndUid(id, userInfo.getId()));
    18. }
    19. @RequestMapping("/update")
    20. public AjaxResult update(ArticleInfo articleInfo, HttpServletRequest request) {
    21. //1.对前端传递的参数进行判空
    22. if (articleInfo == null || articleInfo.getId() <= 0
    23. || !StringUtils.hasLength(articleInfo.getTitle()) || !StringUtils.hasLength(articleInfo.getContent())) {
    24. return AjaxResult.fail(-1, "参数错误!");
    25. }
    26. //2.获取uid进行封装并进行持久化
    27. UserInfo userInfo = UserSessionTools.getLoginUser(request);
    28. if (userInfo == null || userInfo.getId() <= 0) {
    29. return AjaxResult.fail(-1, "参数错误!");
    30. }
    31. articleInfo.setUid(userInfo.getId());
    32. articleInfo.setUpdatetime(LocalDateTime.now());
    33. //3.给前端返回数据
    34. int result = articleService.update(articleInfo);
    35. return AjaxResult.success(result);
    36. }

    根据文章id进行查找文章时,必须要进行校验,确保查询到的文章时该登录用户的文章,即文章表中的uid = 用户表中的id.

  • 相关阅读:
    continue和break的区别与用法
    [附源码]java毕业设计火车票预订系统论文2022
    NSS [NCTF 2018]小绿草之最强大脑
    华为机考:HJ43 迷宫问题
    【linux】日常用的linux命令总结
    灰度发布、蓝绿发布、滚动发布
    机器学习算法(7)—— 朴素贝叶斯算法
    4、FFmpeg命令行操作7
    接口调试工具概论
    SAP-MM-收货操作报错123
  • 原文地址:https://blog.csdn.net/qq_47908816/article/details/133483972