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

news2025/1/13 15:42:03

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

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

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

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

项目亮点:

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

一, 项目的搭建

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

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

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

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.16</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>blogsystem</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>blogsystem</name>
    <description>blogsystem</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.3.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter-test</artifactId>
            <version>2.3.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

2,书写配置文件

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

#配置数据库连接信息
spring:
  datasource:
    url: "你自己的数据库"
    username: root
    password: "数据库对应的密码"
    driver-class-name: com.mysql.cj.jdbc.Driver

#配置Mapper的xml文件存储信息
mybatis:
#  xml的存储位置
  mapper-locations: classpath:mapper/*Mapper.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

#配置日志打印级别
logging:
  level:
    com:
      example:
        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包下:

package com.example.blogsystem.common;

import lombok.Data;

import java.io.Serializable;

/**
 * 统一返回对象
 * 返回成功的话 code 设置成 200
 * 反悔失败的话 code 设置成本身的 code
 */

@Data
public class AjaxResult implements Serializable {
    private int code;
    private String msg;
    private Object data;

    /**
     * 返回成功
     *
     * @param data
     * @return
     */
    public static AjaxResult success(Object data) {
        AjaxResult ajaxResult = new AjaxResult();
        ajaxResult.setCode(200);
        ajaxResult.setMsg("");
        ajaxResult.setData(data);
        return ajaxResult;
    }

    public static AjaxResult success(Object data, String msg) {
        AjaxResult ajaxResult = new AjaxResult();
        ajaxResult.setCode(200);
        ajaxResult.setMsg(msg);
        ajaxResult.setData(data);
        return ajaxResult;
    }

    /**
     * 返回失败
     *
     * @param code
     * @param msg
     * @return
     */

    public static AjaxResult fail(Integer code, String msg) {
        AjaxResult ajaxResult = new AjaxResult();
        ajaxResult.setCode(code);
        ajaxResult.setMsg(msg);
        ajaxResult.setData("");
        return ajaxResult;
    }

    public static AjaxResult fail(Integer code, String msg, String data) {
        AjaxResult ajaxResult = new AjaxResult();
        ajaxResult.setCode(code);
        ajaxResult.setMsg(msg);
        ajaxResult.setData(data);
        return ajaxResult;
    }
}

2, 注册功能

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

前端

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>注册页面</title>
    <link rel="stylesheet" href="css/conmmon.css">
    <link rel="stylesheet" href="css/login.css">
    <!-- 引入jquery的js文件 -->
    <script src="js/jquery.min.js"></script>
</head>

<body>
<!-- 导航栏 -->
<div class="nav">
    <img src="img/logo2.jpg" alt="">
    <span class="title">我的博客系统</span>
    <!-- 用来占据中间位置 -->
    <span class="spacer"></span>
    <a href="blog_list.html">主页</a>
    <a href="login.html">登陆</a>
    <!-- <a href="#">注销</a> -->
</div>
<!-- 版心 -->
<div class="login-container">
    <!-- 中间的注册框 -->
    <div class="login-dialog">
        <h3>注册</h3>
        <div class="row">
            <span>用户名</span>
            <input type="text" id="username">
        </div>
        <div class="row">
            <span>密码</span>
            <input type="password" id="password">
        </div>
        <div class="row">
            <span>确认密码</span>
            <input type="password" id="password2">
        </div>
        <div class="row">
            <button id="submit" onclick="mysub()">提交</button>
        </div>
    </div>
</div>

<!-- 最好把js的代码写在这下面 因为js代码的代码是按照从上往下执行的 如果写在上面的话 可能js的代码获取的一些标签在下面还没有生效 会获取失败 -->
<script>
    function mysub(){
        // 1.非空判断
        // 1.1 先得到输入的组件
        var username = jQuery("#username");
        var password = jQuery("#password");
        var password2 = jQuery("#password2");
        // 1.2 判断输入组件是否为空
        if(username.val().trim()==""){
            alert("请先输入用户名!");
            username.focus(); // 聚焦光标
            return false;
        }
        if(password.val().trim()==""){
            alert("请先输入密码!");
            password.focus();
            return false;
        }
        if(password2.val().trim()==""){
            alert("请先输入确认密码!");
            password2.focus();
            return false;
        }
        if(password.val()!=password2.val()){
            alert("两次密码输入不一致性,请先检查!");
            password.focus();
            return false;
        }
        // 2.先把提交按钮设置成不可用(禁用)
        jQuery("#submit").attr("disabled","disabled");
        // 3.将当前页面的数据提交给后端
        jQuery.ajax({
            url:"/user/reg",
            type:"POST",
            data:{
                "username":username.val().trim(),
                "password":password.val().trim()
            },
            success:function(res){
                // 4.根据后端返回的结果(成功or失败)再处理后续流程
                if(res.code==200 && res.data==1){
                    alert("注册成功!");
                    location.href = "login.html"; // 调整到登录页面
                }else{
                    alert("抱歉:操作失败!"+res.msg);
                    // 取消禁用
                    jQuery("#submit").removeAttr("disabled");
                }
            }
        });
    }
</script>
</body>

</html>

 前端给后端发送请求都是通过Ajax来实现的,所以对于任何需要发送Ajax请求的页面都需要导入js的依赖(后面就不再赘述)

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

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

3, 登录功能 

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

前端

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登陆页面</title>
    <link rel="stylesheet" href="css/conmmon.css">
    <link rel="stylesheet" href="css/login.css">
    <script src="js/jquery.min.js"></script>
</head>

<body>
<!-- 导航栏 -->
<div class="nav">
    <img src="img/logo2.jpg" alt="">
    <span class="title">我的博客系统</span>
    <!-- 用来占据中间位置 -->
    <span class="spacer"></span>
    <a href="blog_list.html">主页</a>
    <a href="reg.html">注册</a>
</div>
<!-- 版心 -->
<div class="login-container">
    <!-- 中间的登陆框 -->
    <div class="login-dialog">
        <h3>登陆</h3>
        <div class="row">
            <span>用户名</span>
            <input type="text" id="username">
        </div>
        <div class="row">
            <span>密码</span>
            <input type="password" id="password">
        </div>
        <div class="row">
            <button id="submit" onclick="mysub()">提交</button>
        </div>
    </div>
</div>

<script>
    function mysub() {
        //1.对提交的数据进行判空操作
        var username = jQuery("#username");
        var password = jQuery("#password");
        if(username.val().trim() == "") {
            alert("请输入用户名!");
            username.focus();
            return false;
        }
        if(password.val().trim() == "") {
            alert("请输入密码!");
            password.focus();
            return false;
        }

        //2.发送数据给服务器
        jQuery.ajax({
            url:"user/login",
            type:"post",
            data:{
                "username":username.val().trim(),
                "password":password.val().trim()
            },
            success:function(res) {
                //这里针对服务器的响应数据 规定返回1是成功 返回0是失败
                if(res.code == 200 && res.data == 1) {
                    alert("恭喜:登录成功!");
                    location.href = "myblog_list.html";
                } else {
                    alert("抱歉:登录失败!" + res.msg);
                    return false;
                }
            }
        });
    }
</script>
</body>

</html>

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

后端

@RequestMapping("/login")
    public AjaxResult login(String username, String password, HttpServletRequest request) {
        //1.对前端传递来的参数进行校验
        if (!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
            return AjaxResult.fail(-1, "参数有误!");
        }

        //2.根据用户名去数据库中进行查询
        UserInfo userInfo = userService.login(username);
        if (userInfo == null || userInfo.getId() <= 0) {
            return AjaxResult.fail(-2, "用户名或者密码错误!");
        }

        //对数据库中查找的密码进行解密
//        if (!PasswordTools.check(password,userInfo.getPassword())) {
//            return AjaxResult.fail(-2, "用户名或者密码错误!");
//        }
        
        if (!userInfo.getPassword().equals(password)) {
            return AjaxResult.fail(-2, "用户名或者密码错误!");
        }

        //当前表示登陆成功 需要存储session
        HttpSession session = request.getSession();
        session.setAttribute(ApplicationVariable.USERINFO_SESSION_KEY, userInfo);
        return AjaxResult.success(1);
    }

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

package com.example.blogsystem.common;

public class ApplicationVariable {

    public static final String USERINFO_SESSION_KEY = "USERINFO_SESSION_KEY";
}

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

package com.example.blogsystem.common;

import com.example.blogsystem.entity.UserInfo;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

public class UserSessionTools {
    public static UserInfo getLoginUser(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute(ApplicationVariable.USERINFO_SESSION_KEY) != null) {
            return (UserInfo) session.getAttribute(ApplicationVariable.USERINFO_SESSION_KEY);
        }

        return null;
    }
}

4, 实现拦截器

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

实现HandlerInterceptor接口:

package com.example.blogsystem.config;

import com.example.blogsystem.common.ApplicationVariable;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;


@Configuration
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(ApplicationVariable.USERINFO_SESSION_KEY) != null) {
            return true;
        }

        response.sendRedirect("/login.html");//没有通过拦截器的请求需要跳转到登录页面先登录
        return false;
    }
}

实现WebMvcConfigurer进行配置接口:

package com.example.blogsystem.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

@Configuration
public class MyConfig implements WebMvcConfigurer {

    @Resource
    private LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor)
                .addPathPatterns("/**") //拦截所有的url
                .excludePathPatterns("/login.html")
                .excludePathPatterns("/reg.html")
                .excludePathPatterns("/blog_list.html")
                .excludePathPatterns("/blog_content.html")
                .excludePathPatterns("/css/**")
                .excludePathPatterns("/editor.md/**")
                .excludePathPatterns("/img/**")
                .excludePathPatterns("/js/**")
                .excludePathPatterns("/user/reg")
                .excludePathPatterns("/user/login");
    }
}

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

5, 博客添加功能

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

前端

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文章添加</title>

    <!-- 引入自己写的样式 -->
    <link rel="stylesheet" href="css/conmmon.css">
    <link rel="stylesheet" href="css/blog_edit.css">

    <!-- 引入 editor.md 的依赖 -->
    <link rel="stylesheet" href="editor.md/css/editormd.min.css" />
    <script src="js/jquery.min.js"></script>
    <script src="editor.md/editormd.js"></script>
</head>

<body>
    <!-- 导航栏 -->
    <div class="nav">
        <img src="img/logo2.jpg" alt="">
        <span class="title">我的博客系统</span>
        <!-- 用来占据中间位置 -->
        <span class="spacer"></span>
        <a href="blog_list.html">主页</a>
        <a href="#">注销</a>
    </div>
    <!-- 编辑框容器 -->
    <div class="blog-edit-container">
        <!-- 标题编辑区 -->
        <div class="title">
            <input id="title" type="text" placeholder="在这里写下文章标题">
            <button onclick="mysub()">发布文章</button>
        </div>
        <!-- 创建编辑器标签 -->
        <div id="editorDiv">
            <textarea id="editor-markdown" style="display:none;"></textarea>
        </div>
    </div>

    <script>
        var editor;
        function initEdit(md){
            // 编辑器设置
            editor = editormd("editorDiv", {
                // 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉. 
                width: "100%",
                // 高度 100% 意思是和父元素一样高. 要在父元素的基础上去掉标题编辑区的高度
                height: "calc(100% - 50px)",
                // 编辑器中的初始内容
                markdown: md,
                // 指定 editor.md 依赖的插件路径
                path: "editor.md/lib/",
                saveHTMLToTextarea: true // 
            });
        }
        initEdit("# 在这里写下一篇博客"); // 初始化编译器的值
        // 提交
        function mysub(){
            //1.对文章标题和内容进行判空操作
            var title = jQuery("#title");
            var content = editor.getValue();
            if(title.val().trim() == "") {
                alert("请输入文章标题!");
                title.focus();
                return false;
            }
            if(content == "") {
                alert("请输入正文!")
                return false;
            }
            //2.提交数据给后端
            jQuery.ajax({
                url:"/art/add",
                type:"post",
                data:{
                    "title":title.val(),
                    "content":content
                },
                success:function (res) {
                    //假设文章添加成功后端给前端返回的data中的数据是1
                    if(res.code == 200 && res.data == 1) {
                        alert("恭喜:文章添加成功!");
                        if (confirm("是否继续添加文章?")) {
                            //如果继续继续添加文章的话 需要刷新此页面
                            location.href = location.href;
                        } else {
                            //不继续添加文章需要跳转到文章列表页
                            location.href = "myblog_list.html";
                        }
                    } else{
                        alert("抱歉:文章添加失败!" + res.msg);
                    }
                }
            })
            // alert(editor.getValue()); // 获取值
            // editor.setValue("#123") // 设置值
        }
    </script>
</body>

</html>

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

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

后端

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

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

6, 博客编辑功能

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

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

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

前端

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>文章修改</title>

    <!-- 引入自己写的样式 -->
    <link rel="stylesheet" href="css/conmmon.css">
    <link rel="stylesheet" href="css/blog_edit.css">

    <!-- 引入 editor.md 的依赖 -->
    <link rel="stylesheet" href="editor.md/css/editormd.min.css"/>
    <script src="js/jquery.min.js"></script>
    <script src="editor.md/editormd.js"></script>
    <script src="js/urltools.js"></script>
    <script src="js/logout.js"></script>
</head>

<body>
<!-- 导航栏 -->
<div class="nav">
    <img src="img/logo2.jpg" alt="">
    <span class="title">我的博客系统</span>
    <!-- 用来占据中间位置 -->
    <span class="spacer"></span>
    <a href="blog_list.html">主页</a>
    <a href="javascript:logout()">注销</a>
</div>
<!-- 编辑框容器 -->
<div class="blog-edit-container">
    <!-- 标题编辑区 -->
    <div class="title">
        <input id="title" type="text" placeholder="在这里写下文章标题">
        <button onclick="mysub()">发布文章</button>
    </div>
    <!-- 创建编辑器标签 -->
    <div id="editorDiv">
        <textarea id="editor-markdown" style="display:none;"></textarea>
    </div>
</div>

<script>
    var isSubmit = 1;
    var id = 0;
    var editor;

    function initEdit(md) {
        // 编辑器设置
        editor = editormd("editorDiv", {
            // 这里的尺寸必须在这里设置. 设置样式会被 editormd 自动覆盖掉.
            width: "100%",
            // 高度 100% 意思是和父元素一样高. 要在父元素的基础上去掉标题编辑区的高度
            height: "calc(100% - 50px)",
            // 编辑器中的初始内容
            markdown: md,
            // 指定 editor.md 依赖的插件路径
            path: "editor.md/lib/",
            saveHTMLToTextarea: true //
        });
    }

    // initEdit("# 在这里写下一篇博客"); // 初始化编译器的值
    //接口1:先去查询当前文章的信息进行展示 页面加载的时候进行调用
    function initArt() {
        //1.通过queryString获取文章id
        id = getParamByKey("id");
        if (id == null || id <= 0) {
            isSubmit = 0;
            alert("抱歉:非法参数!");
            return false;
        }
        //2.向后端发送请求 获取对应id的文章并展现在前端页面
        jQuery.ajax({
            url: "/art/getdetailbyid",
            type: "post",
            data: {
                "id": id
            },
            success: function (res) {
                if (res.code == 200 && res.data != null && res.data.id > 0) {
                    //文章查询成功
                    jQuery("#title").val(res.data.title);
                    initEdit(res.data.content);
                } else {
                    //文章获取失败
                    isSubmit = 0;
                    alert("抱歉:非法参数!" + res.msg);
                }
            }
        });
    }

    initArt();

    //接口2:提交修改操作 触发提交按钮的时候进行调用
    // 提交
    function mysub() {
        if (isSubmit == 0) {
            alert("抱歉:非法操作,请刷新页面再试!");
            return false;
        }
        //1.非空判断
        var title = jQuery("#title");
        var content = editor.getValue();
        if (title.val().trim() == "") {
            alert("请输入文章标题!");
            title.focus();
            return false;
        }
        if (content == "") {
            alert("请输入正文!")
            return false;
        }

        //2.提交请求给后端
        jQuery.ajax({
            url: "/art/update",
            type: "post",
            data: {
                "id": id,
                "title": title.val(),
                "content": content
            },
            success: function (res) {
                //规定修改成功后端返回1
                if (res.code == 200 && res.data == 1) {
                    alert("恭喜:修改成功!");
                    location.href = "myblog_list.html";
                } else {
                    alert("抱歉:非法参数!" + res.msg);
                }
            }
        });
        // alert(editor.getValue()); // 获取值
        // editor.setValue("#123") // 设置值
    }
</script>
</body>

</html>

后端

/**
     * 对文章进行修改的时候也需要对拿到文章进行权限验证 拿到的文章的uid必须和登录的用户的id一致
     * 防止登录的用户对其他人的文章进行篡改
     */

    @RequestMapping("/getdetailbyid")
    public AjaxResult getdetailbyid(Integer id, HttpServletRequest request) {
        //1.对id进行判空操作
        if (id == null || id <= 0) {
            return AjaxResult.fail(-1, "参数错误!");
        }

        //2.获取到登录用户的id
        UserInfo userInfo = UserSessionTools.getLoginUser(request);
        if (userInfo == null || userInfo.getId() <= 0) {
            return AjaxResult.fail(-1, "参数错误!");
        }

        //3.封装id和uid进行持久化操作
        return AjaxResult.success(articleService.getDetailByIdAndUid(id, userInfo.getId()));
    }

    @RequestMapping("/update")
    public AjaxResult update(ArticleInfo articleInfo, HttpServletRequest request) {
        //1.对前端传递的参数进行判空
        if (articleInfo == null || articleInfo.getId() <= 0
                || !StringUtils.hasLength(articleInfo.getTitle()) || !StringUtils.hasLength(articleInfo.getContent())) {
            return AjaxResult.fail(-1, "参数错误!");
        }
        //2.获取uid进行封装并进行持久化
        UserInfo userInfo = UserSessionTools.getLoginUser(request);
        if (userInfo == null || userInfo.getId() <= 0) {
            return AjaxResult.fail(-1, "参数错误!");
        }
        articleInfo.setUid(userInfo.getId());
        articleInfo.setUpdatetime(LocalDateTime.now());
        //3.给前端返回数据
        int result = articleService.update(articleInfo);
        return AjaxResult.success(result);
    }

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1058025.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【重拾C语言】二、顺序程序设计(基本符号、数据、语句、表达式、顺序控制结构、数据类型、输入/输出操作)

目录 前言 二、顺序程序设计 2.1 求绿化带面积——简单程序 2.2基本符号&#xff1a; 2.2.1 字符集 可视字符 不可视字符 2.2.2 C特定符 关键字 分隔符 运算符 2.2.3 标识符 2.2.4 间隔符 2.2.5 注释 2.3 数据 2.3.1 字面常量&#xff08;Literal Constants&am…

Flutter+SpringBoot实现ChatGPT流实输出

FlutterSpringBoot实现ChatGPT流式输出、上下文了连续对话 最终实现Flutter的流式输出上下文连续对话。 这里就是提供一个简单版的工具类和使用案例&#xff0c;此处页面仅参考。 服务端 这里直接封装提供工具类&#xff0c;修改自己的apiKey即可使用&#xff0c;支持连续…

FOC程序cubemx配置-ADC配置

具体配置步骤大家参考&#xff1a;这篇文章 我配置后用keil5自带的仿真工具查看引脚波形&#xff0c;在这里写一下遇到的问题。 1、波形仿真的时候出现 Unknown Signal&#xff1a;参考 这篇文章 2、生成的波形并不完全互补。 PS&#xff1a;出现以上这种情况时&#xff0…

【斗罗大陆2】动画新增12集备案,冰碧帝皇蝎形象被吐槽遭狂喷!

Hello,小伙伴们&#xff0c;我是小郑继续为大家深度解析斗罗大陆2绝世唐门。 《斗罗大陆2》动画新增12集备案 《斗罗大陆2》动画正在如火如荼的上映着&#xff0c;《斗罗大陆2》动画也在同步新增了。 在2023年9月全国重点网络动画片规划备案通过剧目信息中&#xff0c;《斗罗大…

【计算机网络】高级IO之select

文章目录 1. 什么是IO&#xff1f;什么是高效 IO? 2. IO的五种模型五种IO模型的概念理解同步IO与异步IO整体理解 3. 阻塞IO4. 非阻塞IOsetnonblock函数为什么非阻塞IO会读取错误&#xff1f;对错误码的进一步判断检测数据没有就绪时&#xff0c;返回做一些其他事情完整代码myt…

【算法|动态规划No.8】leetcode面试题 17.16. 按摩师

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【手撕算法系列专栏】【LeetCode】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&#xff0c;希望…

Python使用词云图展示

网上看到一个txt文本信息&#xff0c;共2351条饭否记录&#xff0c;据说是微信之父每天发的饭否记录&#xff0c;其实我不知道什么是饭否。我读取这个文本内容&#xff0c;展示到词语图上。之前也使用过&#xff0c;但是好久没有玩Python了&#xff0c;称假期空闲&#xff0c;练…

【从入门到起飞】IO高级流(1)(缓冲流,转换流,序列化流,反序列化流)

&#x1f38a;专栏【JavaSE】 &#x1f354;喜欢的诗句&#xff1a;天行健&#xff0c;君子以自强不息。 &#x1f386;音乐分享【如愿】 &#x1f384;欢迎并且感谢大家指出小吉的问题&#x1f970; 文章目录 &#x1f384;缓冲流&#x1f354;字节缓冲流&#x1f6f8;一次读取…

vue ant 隐藏列

vue ant 隐藏列 重要代码 type: FormTypes.hidden{ title: 序号, key: barCode, width: 10%, type: FormTypes.hidden},

YTM32的电源管理与低功耗系统详解

YTM32的电源管理与低功耗系统详解 苏勇&#xff0c;2023年10月 文章目录 YTM32的电源管理与低功耗系统详解缘起原理与机制电源管理模型的功耗模式正常模式&#xff08;Normal&#xff09;休眠模式&#xff08;Sleep&#xff09;深度休眠模式&#xff08;DeepSleep&#xff09;…

树概念及结构

.1树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因 为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。 有一个特殊的结点&#xff0c;称为根结点&a…

新手学习笔记-----编译和链接

目录 1. 翻译环境和运⾏环境 2. 翻译环境&#xff1a;预编译编译汇编链接 2.1 预处理 2.2 编译 2.2.1 词法分析 2.2.2 语法分析 2.2.3 语义分析 2.3 汇编 2.4 链接 3. 运⾏环境 1. 翻译环境和运⾏环境 在ANSI C的任何⼀种实现中&#xff0c;存在两个不同的环境。 第…

【Leetcode】 131. 分割回文串

给你一个字符串 s&#xff0c;请你将 s 分割成一些子串&#xff0c;使每个子串都是 回文串 。返回 s 所有可能的分割方案。 回文串 是正着读和反着读都一样的字符串。 示例 1&#xff1a; 输入&#xff1a;s "aab" 输出&#xff1a;[["a","a"…

华为云云耀云服务器L实例评测|Huawei Cloud EulerOS 自动化环境部署

[toc] Huawei Cloud EulerOS 自动化环境部署 云耀云服务器L实例【Huawei Cloud EulerOS 2.0 64bit】 Python Git Google Chrome Chromedriver Selenium More… 1. Python 镜像创建后自带。 2.Git 拉取项目。 sudo yum install git3. Google Chrome 使用root权限或sudo权…

WinFroms基于三层构造设计模式的框架所设计的代码生成器1.0

软件开发模式——三层架构 此文章需要在读懂 以上这篇架构模式的基础上再继续往下深入学习简化 目录 1.前言 2.框架准备 3 .coboBox的数据绑定 4.创建文件夹 5.工具方法 6.生成一个数据库访问助手类SqlHelper 7.生成模型层Model 8.生成数据访问层DAL层 9.生成业务…

VD6283TX环境光传感器驱动开发(3)----测试闪烁频率代码

VD6283TX环境光传感器驱动开发----3.测试闪烁频率代码 概述视频教学样品申请源码下载参考代码开发板设置测试结果 概述 ST提供了6283A1_AnalogFlicker代码在X-NUCLEO-6283A1获取闪烁频率&#xff0c;同时移植到VD6283TX-SATEL。 闪烁频率提取主要用于检测光源的闪烁频率&#…

在pycharm中出现下载软件包失败的解决方法

一. 一般情况下我们会选择在设置中下载软件包,过程如下. 1. 直接点击左上角的文件, 再点击设置, 再点击项目, 在右边选择python解释器,点击号,输入要下载的软件包, 在下面的一系列的包中选择相对应的包,点击安装就可以了,有的时候我们下载的是最新的版本,如果要下载固定的版本…

数据结构与算法——19.红黑树

这篇文章我们来讲一下红黑树。 目录 1.概述 1.1红黑树的性质 2.红黑树的实现 3.总结 1.概述 首先&#xff0c;我们来大致了解一下什么是红黑树 红黑树是一种自平衡的二叉查找树&#xff0c;是一种高效的查找树。红黑树具有良好的效率&#xff0c;它可在 O(logN) 时间内完…

爬虫HTTP代理:获取多种类型数据的神器

爬虫HTTP代理是一种常用的数据采集工具&#xff0c;它可以帮助用户获取各种类型的数据。以下是爬虫HTTP代理能获取的一些常见数据&#xff1a; 网页数据 爬虫HTTP代理最常用的功能就是获取网页数据。通过代理服务器&#xff0c;用户可以获取到被封锁或限制访问的网站数据&…

pytorch之nn.Conv1d详解

自然语言处理中一个句子序列&#xff0c;一维的&#xff0c;所以使用Conv1d