Project - blogging system


Preface
insert image description here
The front end of this blog system is divided into 8 pages, which are registration page, login page, edit page, modification page, personal home page, blog text page, draft list page, blog list page

Project advantages

  1. Framework: use ssm (SpringBoot + SpringMVC + MyBatis)
  2. Password: The password used by the user to log in is processed using the salt algorithm and then stored in the database
  3. Persistence of user login status: persist session to redis
  4. Function upgrade: implement a pagination function on the blog list page
  5. Use interceptors for user login verification, unified data return format, and unified exception handling

project creation

Create a SpringBoot project and add the required dependencies

<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.session</groupId>
			<artifactId>spring-session-data-redis</artifactId>
		</dependency>

		<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.0</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>
	</dependencies>

Create corresponding directories, files, tables, and import front-end resources

First create a table in the database, here directly provide the sql statement

-- 创建数据库
drop database if exists mycnblog;
create database mycnblog DEFAULT CHARACTER SET utf8mb4;

-- 使用数据数据
use mycnblog;

-- 创建表[用户表]
drop table if exists  userinfo;
create table userinfo(
    id int primary key auto_increment,
    username varchar(100) not null,
    password varchar(32) not null,
    photo varchar(500) default '',
    createtime datetime default now(),
    updatetime datetime default now(),
    `state` int default 1
) default charset 'utf8mb4';

-- 创建文章表
drop table if exists  articleinfo;
create table articleinfo(
    id int primary key auto_increment,
    title varchar(100) not null,
    content text not null,
    createtime datetime default now(),
    updatetime datetime default now(),
    uid int not null,
    rcount int not null default 1,
    `state` int default 1
)default charset 'utf8mb4';


-- 添加一个用户信息
INSERT INTO `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES
(1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);

-- 文章添加测试数据
insert into articleinfo(title,content,uid,state)
    values('喜讯','今天是正月初六',9,2);

  1. Entity layer (model): Create entity classes UserInfo, ArticleInfo
  2. Control layer (controller): create controller UserController and ArticleInfo
  3. Service layer (servlce): Create service classes: UserService and ArticleService
  4. Persistence layer (mapper): Create interfaces UserMapper and ArticleMapper, and create corresponding xml files, and configure them in yml files
  5. Tool layer (common): unified return class (ResponseAdvice, AjaxResult, etc.)

Note: After creating the corresponding class, you must add the annotations that need to be added, and the
directory structure after creation will not be shown here.
insert image description here

Contents of the yml configuration file

# 配置数据库的连接字符串
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
# 设置 Mybatis 的 xml 保存路径
mybatis:
  mapper-locations: classpath:mybatis/**Mapper.xml
  configuration: # 配置打印 MyBatis 执行的 SQL
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 配置打印 MyBatis 执行的 SQL
logging:
  level:
    com:
      example:
        demo: debug

Import the front-end stuff into static
insert image description here

Implement the common tool class

insert image description here
Since we will basically use the tool class later, let’s implement it here first.

Implement the interceptor to verify user login

  1. Step 1 Create a Constant class and define the key of the session
  2. Step 2: Create a common class that implements the HandlerInterceptor interface and override preHandle
  3. Step 3: Create a common class to implement the WebMvcConfigurer interface and override addInterceptors
public class Constant {
    
    
    //登录信息存储到session中的key值
    public static final String SESSION_USERINFO_KEY = "session_userinfo_key";
}

@Component
public class LoginInterceptor implements HandlerInterceptor {
    
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
    
        //判断登录业务
        HttpSession session = request.getSession(false);//会根据请求发送来的sessionId去服务器找对应的会话
        if(session.getAttribute(Constant.SESSION_USERINFO_KEY)!=null) {
    
    //根据key拿到value
            return true;
        }
        response.setStatus(401);
        return false;
    }
}

@Configuration
public class AppConfig implements WebMvcConfigurer {
    
    
    //不拦截的url
    List<String> excludes = new ArrayList<>() {
    
    {
    
    
        add("/**/*.html");
        add("/js/**");
        add("/editor.md/**");
        add("/css/**");
        add("/img/**"); // 放行 static/img 下的所有文件
    }};

    @Autowired
    LoginInterceptor loginInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    
    
        //配置拦截器
        InterceptorRegistration registration = registry.addInterceptor(loginInterceptor);
        registration.addPathPatterns("/**");
        registration.excludePathPatterns(excludes);
    }
}

Implement a unified data return format

Step 1: Create an ordinary class to implement the method of returning business success and the method returning business failure
Step 2: Create an ordinary class to implement the ResponseAdvice interface, and rewrite the
code of the supports method and beforeBodyWrite method

public class AjaxResult {
    
    

    /**
     * 业务执行成功返回的方法
     * @param data
     * @return
     */
    public static HashMap<String,Object> success(Object data) {
    
    
        HashMap<String,Object> result = new HashMap<>();
        result.put("code",200);
        result.put("msg","");
        result.put("data",data);
        return result;
    }

    /**
     * 业务执行成功时返回的代码
     * @param data
     * @param msg
     * @return
     */
    public static HashMap<String,Object> success(Object data,String msg) {
    
    
        HashMap<String,Object> result = new HashMap<>();
        result.put("code",200);
        result.put("msg",msg);
        result.put("data",data);
        return result;
    }

    /**
     * 业务执行失败返回的方法
     * @param code
     * @param data
     * @param msg
     * @return
     */
    public static HashMap<String,Object> fail(int code,Object data,String msg) {
    
    
        HashMap<String,Object> result = new HashMap<>();
        result.put("code",code);
        result.put("msg",msg);
        result.put("data",data);
        return result;
    }

    /**
     * 业务执行失败返回的方法
     * @param code
     * @param msg
     * @return
     */
    public static HashMap<String,Object> fail(int code,String msg) {
    
    
        HashMap<String,Object> result = new HashMap<>();
        result.put("code",code);
        result.put("msg",msg);
        result.put("data","");
        return result;
    }
}
public class ResponseAdvice implements ResponseBodyAdvice {
    
    
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
    
    
        return true;
    }

    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    
    
        if(body instanceof HashMap) {
    
    
            return body;
        }
        //如果body是字符串类型,需要特殊处理
        if(body instanceof String) {
    
    
            ObjectMapper objectMapper = new ObjectMapper();
            return objectMapper.writeValueAsString(AjaxResult.success(body));
        }
        return AjaxResult.success(body);
    }
}

Implement salt encryption class

Create an ordinary class and implement two methods, the encrypt method and the decrypt method.

  • The encrypt method performs salt encryption based on the password entered by the user, and returns the final encrypted password
  • The decrypt method verifies the password entered by the user and the encrypted password of the database
public class SecurityUtil {
    
    

    /**
     * 对password进行加盐加密
     * @param password
     * @return
     */
    public static String encrypt(String password) {
    
    
        
    }

    /**
     * 验证password和数据库拿出来的finalPassword进行验证
     * @param password
     * @param finalPassword
     * @return
     */
    public static String decrypt(String password,String finalPassword) {
    
    
        
    }
}

Implement the encrypt method

The idea of ​​adding salt: use the UUID class to generate a 32-length string as the salt value, then perform md5 encryption on the salt value + password to generate a 32-length string, and then salt value + this md5-encrypted string is the final result

 /**
     * 对password进行加盐加密
     * @param password
     * @return
     */
    public static String encrypt(String password) {
    
    
        //每次生成内容不同,但是长度固定为32的字符串
        String salt = UUID.randomUUID().toString().replace("-","");
        //盐值+password进行md5加密
        String finalPassword = DigestUtils.md5DigestAsHex((salt+password).getBytes());
        //返回盐值+密码,总共64位,存到数据库中
        return salt+finalPassword;
    }

Implement the decrypt method

This method is used to decrypt and verify the password
idea: this method has two parameters password: the password to be verified, finalPassword: the final correct password (the password queried in the database), extract the salt value from finalPassword, and then perform salt value + password md5 encryption generates a string, and then the salt value + string and finalPassword determine whether they are equal

/**
     * 验证password和数据库拿出来的finalPassword进行验证
     * password:待验证的密码
     * finalPassword:最终正确的密码(在数据库查询的密码)
     * @param password
     * @param finalPassword
     * @return
     */
    public static boolean decrypt(String password,String finalPassword) {
    
    
        //非空效验
        if(!StringUtils.hasLength(password) || !StringUtils.hasLength(finalPassword)) {
    
    
            return false;
        }
        if(finalPassword.length()!=64) {
    
    
            return false;
        }
        //提取出盐值
        String salt = finalPassword.substring(0,32);
        //使用盐值+密码生成一个32位的密码
        String securityPassword = DigestUtils.md5DigestAsHex((salt+password).getBytes());
        //使用上一个32位的密码拼接上盐值进行密码验证
        return (salt+securityPassword).equals(finalPassword);
    }

Implement the SessionUtil class

This tool class is used to query the session information of the current user login

public class SessionUtil {
    
    
    
    public static UserInfo getLoginUser(HttpServletRequest request) {
    
    
        HttpSession session = request.getSession(false);
        if(session!=null && session.getAttribute(Constant.SESSION_USERINFO_KEY)!=null) {
    
    
            UserInfo userInfo = (UserInfo) session.getAttribute(Constant.SESSION_USERINFO_KEY);
            return userInfo;
        }
        return null;
    }
}

Implement the registration page

From the beginning, it involves front-end and back-end interaction, and the interface for front-end and back-end interaction must be agreed

Implement the front-end code

Then write the front-end code first (open reg.html to write the code)
remember to introduce the jquery
insert image description here
mysub method

function mysub() {
    
    
            //1.非空效验
            var username = jQuery("#username");
            var password = jQuery("#password");
            var password2 = jQuery("#password2");
            if(username.val()=="") {
    
    
                alert("用户名为空");
                username.focus();
                return false;
            }
            if(password.val()=="") {
    
    
                alert("密码为空");
                password.focus();
                return false;
            }
            if(password2.val()=="") {
    
    
                alert("确认密码为空");
                password2.focus();
                return false;
            }
            if(password.val()!=password2.val()) {
    
    
                alert("两次输入的密码不一致");
                return false;
            }
            //2.发送ajax请求
            jQuery.ajax({
    
    
                url:"/user/reg",
                type:"POST",
                data:{
    
    
                    username:username.val(),
                    password:password.val()
                },
                success:function(result) {
    
    
                    if(result.code==200 && result.data==1) {
    
    
                        alert("注册成功");
                        if(confirm("是否去登录?")) {
    
    
                            location.href="login.html";
                        }
                    } else if(result.code==-2 && result.msg!=null) {
    
    
                        alert("注册失败,用户名已存在");
                    } else {
    
    
                        alert("注册失败请重试");
                    }
                }
            });
        }

Implement the backend code

From the front-end code, we can see that the url is /user/reg. We need to release this url in AppConfig, because all urls must be intercepted by default, but the registration cannot be intercepted, because how can we log in without registration? The basic idea of ​​the back-end code is
insert image description here
, The controller calls service, and the service calls mapper,
so inject UserService into UserController, inject UserMapper into UserService, and
insert image description here
insert image description here
implement the reg method in UserController

@RequestMapping("/reg")
    public Object reg(String username,String password) {
    
    
        //1.非空效验
        if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
    
    
            return AjaxResult.fail(-1,"非法参数");
        }
        //根据用户名查询用户
        UserInfo userInfo = userService.getUserByName(username);
        if(userInfo!=null && userInfo.getId()>0) {
    
    
            //用户名已经存在
            return AjaxResult.fail(-2,"用户名已存在");
        }
        //2.进行添加操作
        int result = userService.add(username, SecurityUtil.encrypt(password));
        if(result==1) {
    
    
            return AjaxResult.success(1,"添加成功");
        } else {
    
    
            return AjaxResult.fail(-1,"添加失败");
        }
    }

As you can see, we also need to implement the getUserByName and add methods in UserService

public UserInfo getUserByName(String username) {
    
    
        return userMapper.getUserByName(username);
    }

    public int add(String username,String password) {
    
    
        return userMapper.add(username,password);
    }

After the implementation, you also need to define the getUserByName method and the add method in the UserMapper interface, and then implement the sql statement UserMapper in the corresponding xml file

 public UserInfo getUserByName(@Param("username") String username);

    public int add(@Param("username") String username,@Param("password") String password);

Corresponding UserMapper.xml file
insert image description here

 <select id="getUserByName" resultType="com.example.demo.model.UserInfo">
        select * from userinfo where username=#{
    
    username}
    </select>

    <insert id="add">
        insert into userinfo(username,password)
        values(#{
    
    username},#{
    
    password})
    </insert>

The registration page here is completed

Implement the login page

Implement the front-end code

Open login.html to write the code
Still the same, introduce jquery, then set the onclick listener in submit, and then implement the mysub() method
insert image description here

function mysub() {
    
    
            //1.非空效验
            var username = jQuery("#username");
            var password = jQuery("#password");
            if(username.val()=="") {
    
    
                alert("用户名为空");
                username.focus();
                return false;
            }
            if(password.val()=="") {
    
    
                alert("密码为空");
                password.focus();
                return false;
            }
            //2.发送ajax请求
            jQuery.ajax({
    
    
                url:"/user/login",
                type:"POST",
                data:{
    
    
                    "username":username.val(),
                    "password":password.val()
                },
                success:function(result) {
    
    
                    if(result.code==200 && result.data==1) {
    
    
                        location.href="myblog_list.html";
                    } else {
    
    
                        alert("用户名或密码错误");
                        username.focus();
                    }
                }
            });
        }

Implement the backend code

First release the /user/login interface
and then write the login method in UserController

@RequestMapping("/login")
    public int login(HttpServletRequest request,String username,String password) {
    
    
        //1.非空效验
        if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
    
    
            return 0;
        }
        //2.进行数据库查询操作
        UserInfo userInfo = userService.getUserByName(username);
        if(userInfo!=null && userInfo.getId()>0) {
    
    
            //用户名正确,验证密码
            if(SecurityUtil.decrypt(password,userInfo.getPassword())) {
    
    
                //将userInfo存到session中
                HttpSession session = request.getSession(true);
                session.setAttribute(Constant.SESSION_USERINFO_KEY,userInfo);
                return 1;
            }
        }
        
        return 0;
    }

Realize a personal home page

Implement the logout function

Of course, since the logout function should exist in many pages, it may not be described in detail later

Implement the front-end code

Many pages behind the logout function will use
the front-end code

//退出登录
function onExit() {
    
    
    if(confirm("是否确认退出?")) {
    
    
        //发送ajax请求退出
        jQuery.ajax({
    
    
            url:"/user/logout",
            type:"POST",
            data:{
    
    },
            success: function(result) {
    
    
                location.href="login.html";
            },
            error:function(err) {
    
    
                if(err!=null && err.status==401) {
    
    
                    alert("用户未登录,即将跳转登录页面");
                    location.href="login.html";
                }
            }
        });
    }
}

Write the myblog_list.html file
and introduce Tool.js and jquery
insert image description here
and then only need to modify a line of code
insert image description here
When you click to log out, it will call the onExit() method

Implement the backend code

When the user clicks to log out and sends an ajax request to the backend, the backend will delete the corresponding session of the user.

Write code in UserController

@RequestMapping("/logout")
    public boolean logout(HttpServletRequest request) {
    
    
        HttpSession session = request.getSession(false);
        if(session!=null && session.getAttribute(Constant.SESSION_USERINFO_KEY)!=null) {
    
    
            session.removeAttribute(Constant.SESSION_USERINFO_KEY);
        }
        return true;
    }

Initialize personal information (including personal information of personal article list) and complete delete function

When jumping to myblog_list.html, the initList() and myinfo() methods are called on the front end. These two methods send an ajax request to query the database, and then return personal information and articles published by the individual to the front end. The front end then uses the data to render

Implement the front-end code

<script>
        var descLen = 60;//简介最大长度
        //这个方法用来从正文中提取字符串
        function mySubStr(content) {
    
    
            if(content.length>desLen) {
    
    
                return content.substr(0,descLen);
            }
            return content;
        }
        //初始化个人列表信息
        function initList() {
    
    
            jQuery.ajax({
    
    
                url:"/art/mylist",
                type:"POST",
                data:{
    
    },//不用传uid,因为session中有userinfo,不能轻信前端传来的参数
                success:function(result) {
    
    
                    if(result.code==200 && result.data!=null && result.data.length>0) {
    
    
                        var html = "";
                        for(var i=0;i<result.data.length;i++) {
    
    
                            var item = result.data[i];
                            //如果state==2,说明是草稿箱里的文章,不显示出来
                            if(item.state==2) {
    
    
                                continue;
                            }
                            html+='<div class="blog">';
                            html+='<div class="title">'+item.title+'</div>';
                            html+='<div class="date">'+item.createtime+'</div>'+'<div class="desc">'+mySubstr(item.content)+' </div>';
                            html+='<div style="text-align: center;margin-top: 50px;">';
                            html+='<a href="blog_content.html?id='+item.id+'">查看详情</a>&nbsp;&nbsp;';
                            html+='<a href="blog_update.html?id='+item.id+'">修改</a>&nbsp;&nbsp;<a href="javascript:myDel('+item.id+')">删除</a></div>';
                            html+='</div>';
                        }
                        jQuery("#artlistDiv").html(html);
                    } else {
    
    
                        //此人没有发表文章
                        jQuery("#artlistDiv").html("<h1>暂无数据</h1>");
                    }
                },
                error:function(err) {
    
    
                    if(err!=null && err.statue==401) {
    
    
                        alert("用户未登录,即将跳转登录页面");
                        location.href="login.html";
                    }
                }
            });
        }
        initList();//当浏览器渲染引擎执行到此行时就会调用此方法

        //获取个人信息
        function myinfo() {
    
    
            jQuery.ajax({
    
    
                url:"user/myinfo",
                type:"POST",
                data:{
    
    },
                success:function(result) {
    
    
                    if(result.code==200 && result.data!=null) {
    
    
                        jQuery("#username").text(result.data.username);
                    }
                },
                error:function(err) {
    
    
                    if(err!=null && err.status==401) {
    
    
                        alert("用户未登录,即将跳转登录页面");
                        location.href="login.html";
                    }
                }
            });
        }
        myinfo();

        //删除功能
        function myDel(aid) {
    
    
            jQuery.ajax({
    
    
                url:"art/del",
                type:"POST",
                data:{
    
    
                    "aid":aid
                },
                success:function(result) {
    
    
                    if(result.code==200 && result.data==1) {
    
    
                        alert("删除成功");
                        location.href="myblog_list.html";
                    }
                },
                error:function(err) {
    
    
                    if(err!=null && err.status==401) {
    
    
                        alert("用户未登录");
                        location.href="login.html"

                    } else {
    
    
                        alert("删除失败");
                        location.href="myblog_list.html"
                    }
                    
                }
            });
        }

    </script>

The code is very long, but in fact there are only three main methods. These three methods have ajax requests, so they need to be processed at the back end and then return the results

Implement the backend code

First process the ajax request of initList
For the ajax request sent by initList, we need to check the article table according to the userInfo id stored in the session to get all the article information of the user, and then return it. Similarly, inject
first
insert image description here

 @RequestMapping("/mylist")
    public List<ArticleInfo> myList(HttpServletRequest request) {
    
    
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if(userInfo!=null) {
    
    
            return articleService.getMyList(userInfo.getId());
        }
        return null;
    }

Next, implement the getMyList method in ArticleService,
insert image description here
and inject it into ArticleService first.

 public List<ArticleInfo> getMyList(Integer uid) {
    
    
        return articleMapper.getMyList(uid);
    }

Next, define the getMyList method in ArticleMapper, and then write the sql statement in the corresponding xml file

public List<ArticleInfo> getMyList(@Param("uid") Integer uid);
<select id="getMyList" resultType="com.example.demo.model.ArticleInfo">
            select * from articleinfo where uid=#{
    
    uid}
     </select>

Next, handle the ajax request of myinfo

 @RequestMapping("/myinfo")
    public UserInfo myInfo(HttpServletRequest request) {
    
    
        HttpSession session = request.getSession(false);
        if(session!=null && session.getAttribute(Constant.SESSION_USERINFO_KEY)!=null) {
    
    
            UserInfo userInfo = SessionUtil.getLoginUser(request);
            return userInfo;
        }
        return null;
    }

Next, process the ajax request sent by myDel

@RequestMapping("/del")
    public Integer artDel(HttpServletRequest request,Integer aid) {
    
    
        //非空效验
        if(aid!=null && aid>0) {
    
    
            //根据文章id查询文章详情
            ArticleInfo articleInfo = articleService.getDetail(aid);
            //进行归属人验证
            UserInfo userInfo = SessionUtil.getLoginUser(request);
            if(articleInfo!=null && userInfo!=null && userInfo.getId()!=articleInfo.getUid()) {
    
    
                //归属人正确进行删除
                int result = articleService.artDel(aid);
                return result;
            }
        }
        return null;
    }

Next, you have to implement the getDetail method and the artDel method in ArticleService

public ArticleInfo getDetail(Integer aid) {
    
    
        return articleMapper.getDetail(aid);
    }

    public Integer artDel(Integer aid) {
    
    
        return articleMapper.artDel(aid);
    }

Next, define the getDetail method and artDel method in ArticleMapper, and then write the sql statement in the corresponding xml file

public ArticleInfo getDetail(@Param("aid") Integer aid);
 public Integer artDel(@Param("aid") Integer aid);
 <select id="getDetail" resultType="com.example.demo.model.ArticleInfo">
          select * from articleinfo where id=#{
    
    aid}
     </select>

  <delete id="artDel">
          delete from articleinfo where id=#{
    
    aid}
  </delete>

Implementation details page (blog_content.html)

insert image description here
The page effect of myblog_list.html is roughly like this. The delete function has been implemented, so now to realize the view details function, when you click to view the details, it will jump to the blog_content.html page and pass the article id to it (as shown in the figure below )
insert image description here

Implement the front-end code

Next, open blog_content.html to write the front-end code.
This page also has the logout function, which is the same as the above.
insert image description here
Then copy and paste the onExit() method above
. Before writing, we need to write a method getURLParam in Tool.js, This method is used to get the parameters from the url
here directly to the code

// 获取当前 url 中某个参数的方法
function getURLParam(key){
    
    
    var params = location.search;
    if(params.indexOf("?")>=0){
    
    
        params = params.substring(1);
        var paramArr = params.split('&');
        for(var i=0;i<paramArr.length;i++){
    
    
            var namevalues = paramArr[i].split("=");
            if(namevalues[0]==key){
    
    
                return namevalues[1];
            }
        }
    }else{
    
    
        return "";
    }
}

Then start writing blog_content.html

 //获取文章详细信息
            function getArtDetail() {
    
    
                //从url中获取文章id,也就是在myblog_list.html跳转到这里时url中的参数
                var aid = getURLParam("id");
                if(aid!=null && aid>0) {
    
    
                    //发送ajax请求,查询数据库拿到文章详情
                    jQuery.ajax({
    
    
                        url:"art/detail",
                        type:"POST",
                        data:{
    
    "aid":aid},
                        success:function(result) {
    
    
                            if(result.code==200 && result.data!=null) {
    
    
                                var art = result.data;
                                jQuery("#title").text(art.title);
                                jQuery("#date").text(art.createtime);
                                jQuery("#rcount").text(art.rcount);
                                editormd = editormd.markdownToHTML("editorDiv",{
    
    
                                    markdown : art.content
                                });
                                //根据uid获取个人信息
                                myInfo(art.uid);
                            }
                        }
                    });
                }
            }
            getArtDetail();

            //根据uid获取个人信息
            function myInfo(uid) {
    
    
                jQuery.ajax({
    
    
                    url:"user/myinfobyid",
                    type:"POST",
                    data:{
    
    "uid":uid},
                    success:function(result) {
    
    
                        if(result.code==200 && result.data!=null) {
    
    
                            jQuery("#username").text(result.data.username);
                        }
                    },
                    error:function(err) {
    
    
                        if(err!=null && err.status==401) {
    
    
                            alert("用户为登录,即将跳转登录页面");
                            location.href="login.html"
                        }
                    }
                });
            }

Implement the backend code

The above front-end code is also two ajax requests,
first to process the ajax request sent by the getArtDetail method,
and to release the detail interface in AppConfig
insert image description here

 @RequestMapping("/detail")
    public Object getDetail(Integer aid) {
    
    
        if(aid!=null && aid>0) {
    
    
            ArticleInfo articleInfo = articleService.getDetail(aid);
            //访问量加1
            synchronized (locker) {
    
    
                int result = articleService.rountAdd(aid,articleInfo.getContent()+1);
                if(result!=1) {
    
    
                    return AjaxResult.fail(-1,"访问量添加失败");
                }
            }
            //返回文章详情
            return AjaxResult.success(articleInfo);
        }
        return AjaxResult.fail(-1,"查询失败");
    }

Next, implement the rcountAdd method in ArtclieService

public int rcountAdd(Integer aid,Integer rcount) {
    
    
        return articleMapper.rcountAdd(aid,rcount);
    }

Next, define the rcountAdd method in ArticleMapper and write the sql statement in the corresponding xml file

 public int rcountAdd(@Param("aid") Integer aid,@Param("rcount") Integer rcount);
<update id="rcountAdd">
          update articleinfo set rcount=#{
    
    rcount} where id=#{
    
    aid}
     </update>

Then process the ajax request sent by myinfo, and release /user/myinfobyuid in AppConfig
insert image description here

@RequestMapping("/myinfobyuid")
    public UserInfo getMyInfoByUid(Integer uid) {
    
    
        if(uid!=null && uid>0) {
    
    
            return userService.getMyInfoByUid(uid);
        }
        return null;
    }
public UserInfo getMyInfoByUid(Integer uid) {
    
    
        return userMapper.getMyInfoByUid(uid);
    }
public UserInfo getMyInfoByUid(@Param("uid") Integer uid);
<select id="getMyInfoByUid" resultType="com.example.demo.model.UserInfo">
        select * from userinfo where id=#{
    
    uid}
    </select>

Implement blog modification function (blog_update.html)

insert image description here
When you click to modify, it will jump to blog_update.html and pass the id to it

Implement the front-end code

Similarly, this page has a logout function, and the implementation method is the same as above.
insert image description here

 function mysub(){
    
    
            // alert(editor.getValue()); // 获取值
            // editor.setValue("#123") // 设置值\
            var title = jQuery("#title");
            var content = editor.getValue();
            //非空效验
            if(title=="") {
    
    
                title.focus();
                alert("请先输入标题");
                return false;
            }
            if(content=="") {
    
    
                alert("请输入正文");
                return false;
            }
            jQuery.ajax({
    
    
                url:"/art/update",
                type:"POST",
                data:{
    
    
                    "aid":aid,
                    "title":title.val(),
                    "content":content
                },
                success:function(result) {
    
    
                    if(result.code==200 && result.data>0) {
    
    
                        alert("修改成功");
                        location.href="myblog_list.html";
                    } else {
    
    
                        alert("修改失败,请重试");
                    }
                },
                error:function(err) {
    
    
                    if(err!=null && err.status==401) {
    
    
                        alert("用户未登录");
                        location.href="login.html";
                        
                    }
                }
            });
        }

        //查询文章详情并展现
        function showArt() {
    
    
            //从url中获取文章id
            aid = getURLParam("id");
            if(aid!=null && aid>0) {
    
    
                //访问后端详情
                jQuery.ajax({
    
    
                    url:"/art/detailbyid",
                    type:"POST",
                    data:{
    
    "aid":aid},
                    success:function(result) {
    
    
                        if(result.code==200 && result.data!=null) {
    
    
                            var art = result.data;
                            jQuery("#title").val(art.title);
                            initEdit(art.content);
                        } else {
    
    
                            alert("您没有权限修改");
                            location.href="myblog_list.html";
                        }
                    },
                    error:function(err) {
    
    
                        if(err!=null && err.status==401) {
    
    
                            alert("用户还未登录,即将跳转登录页面");
                            location.href="login.html";
                        }
                    }
                });
            }
        }
        showArt();

Implement the backend code

The front-end code above involves two ajax requests.
Let’s first process the ajax request sent by the showArt method.

 @RequestMapping("/detailbyid")
    public Object getDetailById(HttpServletRequest request,Integer aid) {
    
    
        if(aid!=null && aid>0) {
    
    
            //根据文章id查询文章详情
            ArticleInfo articleInfo = articleService.getDetail(aid);
            //文章归属人验证
            UserInfo userInfo = SessionUtil.getLoginUser(request);
            if(articleInfo!=null && userInfo!=null && articleInfo.getUid()==userInfo.getId()) {
    
    
                //文章归属人正确
                return AjaxResult.success(articleInfo);
            }
        }
        return AjaxResult.fail(-1,"查询失败");
    }

Next, process the ajax request sent by mysub

@RequestMapping("/update")
    public int update(HttpServletRequest request,Integer aid,String title,String content) {
    
    
        //非空效验
        if(!StringUtils.hasLength(title) || !StringUtils.hasLength(content) || aid == 0 || aid<=0) {
    
    
            return 0;
        }
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if(userInfo!=null && userInfo.getId()>0) {
    
    
            return articleService.update(aid,userInfo.getId(),title,content);
        }
        return 0;
    }

Then implement update in ArticleService

 public int update(Integer aid,Integer uid,String title,String content) {
    
    
        return articleMapper.update(aid,uid,title,content);
    }

Then define update in ArticleMapper and write sql statement in the corresponding xml file

public int update(@Param("aid") Integer aid,@Param("uid") Integer uid,
                      @Param("title") String title,@Param("content") String content);
<update id="update">
          update articleinfo set title=#{
    
    title},content=#{
    
    content}
          where id=#{
    
    aid} and uid=#{
    
    uid}
     </update>

Implement blog edit page

Implement the front-end code

 function mysub(){
    
    
            // alert(editor.getValue()); // 获取值
            // editor.setValue("#123") // 设置值
            var title = jQuery("#title");
            var content = editor.getValue();
            //非空效验
            if(title=="") {
    
    
                title.focus();
                alert("请先输入标题");
                return false;
            }
            if(content=="") {
    
    
                alert("请先输入正文");
                return false;
            }
            jQuery.ajax({
    
    
                url:"/art/release",
                type:"POST",
                data:{
    
    
                    "title":title.val(),
                    "content":content
                },
                success:function(result) {
    
    
                    if(result.code==200 && result.data>0) {
    
    
                        alert("发布成功");
                        location.href="myblog_list.html";
                    } else {
    
    
                        alert("发布失败,请重试");
                    }
                },
                error:function(err) {
    
    
                    if(err!=null && err.status==401) {
    
    
                        alert("用户未登录,即将跳转登录页面");
                        location.href="login.html";
                    }
                }
            });
        }

        //检查是否已经登录
        function checkIsLogged() {
    
    
            jQuery.ajax({
    
    
                url:"/user/islogged",
                type:"GET",
                success:function(result) {
    
    
                    return true;
                },
                error:function(err) {
    
    
                    alert("用户为登录,即将跳转登录页面");
                    location.href="login.html";
                }
            });
        }
        checkIsLogged();

        //保存文章到草稿箱,将state设置为2
        function draft() {
    
    
            var title = jQuery("#title");
            var content = editor.getValue();
            //非空效验
            if(title=="") {
    
    
                title.focus();
                alert("请先输入标题");
                return false;
            }
            if(content=="") {
    
    
                alert("请先输入正文");
                return false;
            }
            jQuery.ajax({
    
    
                url:"/art/draft",
                type:"POST",
                data:{
    
    
                    "title":title.val(),
                    "content":content
                },
                success:function(result) {
    
    
                    if(result.code==200 && result.data>0) {
    
    
                        alert("保存成功");
                        location.href="myblog_list.html";
                    } else {
    
    
                        alert("发布失败,请重试");
                    }
                },
                error:function(err) {
    
    
                    if(err!=null && err.status==401) {
    
    
                        alert("用户未登录,即将跳转登录页面");
                        location.href="login.html";
                    }
                }
            });
        }

Implement the backend code

The front-end code above involves a total of 3 ajax requests.
Let’s first process the ajax request of the checkIsLogged method, which is mainly used to log in during verification.

 @RequestMapping("/islogged")
    public int isLogged(HttpServletRequest request, HttpServletResponse response) {
    
    
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if(userInfo!=null) {
    
    
            return 1;
        }
        response.setStatus(401);
        return 0;
    }

Then process the ajax request of the mysub() method, which is used to publish articles, the default state1 indicates that it is a published article, state2 is the article saved in the draft box

@RequestMapping("/release")
    public int releaseArt(HttpServletRequest request,String title,String content) {
    
    
        //非空效验
        if(!StringUtils.hasLength(title) || !StringUtils.hasLength(content)) {
    
    
            return 0;
        }
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if(userInfo!=null && userInfo.getId()>0) {
    
    
            int result = articleService.releaseArt(title,content,userInfo.getId());
            if(result==1) {
    
    
                return 1;
            }
        }
        return 0;
    }

It is also necessary to implement the releaseArt method in ArtcileService, then define the releaseArt method in ArticleMapper, and write the sql statement in the corresponding xml file

 @RequestMapping("/release")
    public int releaseArt(HttpServletRequest request,String title,String content) {
    
    
        //非空效验
        if(!StringUtils.hasLength(title) || !StringUtils.hasLength(content)) {
    
    
            return 0;
        }
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if(userInfo!=null && userInfo.getId()>0) {
    
    
            int result = articleService.releaseArt(title,content,userInfo.getId());
            if(result==1) {
    
    
                return 1;
            }
        }
        return 0;
    }
 public int releaseArt(@Param("title")String title,@Param("content") String content,@Param("uid") Integer uid);
<insert id="releaseArt">
          insert into articleinfo (title,content,uid) values (#{
    
    title},#{
    
    content},#{
    
    uid})
     </insert>

Finally, the process of saving the article is the ajax request of draft(). The processing method is to save the article in the database, but set the field state to 2, and then when the front end is rendering, if state==2, the article will be displayed in my draft box

 @RequestMapping("/draft")
    public int draftArt(HttpServletRequest request,String title,String content) {
    
    
        //非空效验
        if(!StringUtils.hasLength(title) || !StringUtils.hasLength(content)) {
    
    
            return 0;
        }
        int state = 2;//将state设置为2
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if(userInfo!=null && userInfo.getId()>0) {
    
    
            //将文章保存到数据库,并将state设置为2,表示还未发布
            int result = articleService.draftArt(title,content,userInfo.getId(),2);
            return result;
        }
            return 0;
    }

The old step is to implement draftArt in ArticleService, then define the draftArt method in ArticleMapper, and write the sql statement in the corresponding xml file

public int draftArt(String title,String content,Integer uid,Integer state) {
    
    
        return articleMapper.draftArt(title,content,uid,state);
    }
 public int draftArt(@Param("title") String title,@Param("content") String content,
                        @Param("uid") Integer uid,@Param("state") Integer state);
 <insert id="draftArt">
          insert into articleinfo (title,content,uid,state) values (#{
    
    title},#{
    
    content},#{
    
    uid},#{
    
    state})
     </insert>

Implement my draft list (draft_list.html)

insert image description here
When I click on my draft box, it will jump to the draft_list.html page

Implement the front-end code

The code of draft_list.html is basically the same as that of myblog_list.html, the main difference is the state2 is still state2

 var descLen = 60;//简介最大长度
    //字符串截取,将文章正文截取成简介
    function mySubstr(content) {
    
    
        if(content.length>descLen) {
    
    
            return content.substr(0,descLen);
        }
        return content;
    }
    //初始化个人列表信息
    function initList() {
    
    
        jQuery.ajax({
    
    
                url:"/art/mylist",
                type:"POST",
                data:{
    
    },//不用传uid,因为session中有userinfo,不能轻信前端的参数
                success:function(result) {
    
    
                   if(result.code==200 && result.data!=null && result.data.length>0) {
    
    
                    //todo:有文章
                    var html="";
                    var count = 0;
                    for(var i=0;i<result.data.length;i++) {
    
    
                        var item=result.data[i];
                        //如果state==2说明是草稿箱里的文章,显示出来
                        if(item.state==2) {
    
    
                            html+='<div class="blog">';
                            html+='<div class="title">'+item.title+'</div>';
                            html+='<div class="date">'+item.createtime+'</div>'+'<div class="desc">'+mySubstr(item.content)+' </div>';
                            html+='<div style="text-align: center;margin-top: 50px;">';
                            html+='<a href="javascript:publish('+item.id+')">发布文章</a>&nbsp;&nbsp;';
                            html+='<a href="blog_update.html?id='+item.id+'">修改</a>&nbsp;&nbsp;<a href="javascript:myDel('+item.id+')">删除</a></div>';
                            html+='</div>';
                            count++;
                        }
                    }
                    jQuery("#artlistDiv").html(html); 
                    if(count==0) {
    
    
                        //此人草稿箱没有文章
                        jQuery("#artlistDiv").html("<h1>暂无数据</h1>");
                    } 
                   } 
                },
                error:function(err) {
    
    
                    if(err!=null&&err.status==401) {
    
    
                        alert("用户未登录,即将跳转登录页面");
                        location.href="login.html";
                    }
                }
            });
    }
    initList();//当浏览器渲染引擎执行到此行时就会调用此方法
    //获取个人信息
    function myInfo() {
    
    
        jQuery.ajax({
    
    
                url:"/user/myinfo",
                type:"POST",
                data:{
    
    },
                success:function(result) {
    
    
                   if(result.code==200 && result.data!=null) {
    
    
                    jQuery("#username").text(result.data.username);
                   }
                },
                error:function(err) {
    
    
                    if(err!=null&&err.status==401) {
    
    
                        alert("用户未登录,即将跳转登录页面");
                        location.href="login.html";
                    }
                }
                
            });
    }
    myInfo();
    //删除功能
    function myDel(aid) {
    
    
        jQuery.ajax({
    
    
            url:"/art/del",
            type:"POST",
            data:{
    
    
                "aid":aid
            },
            success:function(result) {
    
    
                if(result.code==200 && result.data==1) {
    
    
                    alert("删除成功");
                    location.href="draft_list.html";
                    
                }
            },
            error:function(err) {
    
    
                if(err!=null&&err.status==401) {
    
    
                    alert("该用户没有删除权限!");
                } else {
    
    
                    alert("删除失败!");
                }
                location.href="draft_list.html";
            }
        });

    }

    //将草稿箱里的文章发布:将state设置成1
    function publish(aid) {
    
    
        jQuery.ajax({
    
    
            url:"/art/publish",
            type:"POST",
            data:{
    
    
                "aid":aid
            },
            success:function(result) {
    
    
                if(result.code==200 && result.data==1) {
    
    
                    alert("发布成功");
                    location.href="myblog_list.html";
                }
            },
            error:function(err) {
    
    
                if(err!=null&&err.status==401) {
    
    
                    alert("该用户没有发布权限!");
                } else {
    
    
                    alert("发布失败!");
                }
                location.href="draft_list.html";
            }
        });
    }
    

Implement the backend code

Of the three ajax requests involved above, the first two ajax requests have been processed in the previous backend code, so here we only need to process the ajax request in the publish method

 @RequestMapping("/publish")
    public int publishArt(Integer aid) {
    
    
        if(aid!=null) {
    
    
            //将state设置为1
            int result = articleService.publishArt(aid,1);
            if(result==1) {
    
    
                return 1;
            }
        }
        return 0;
    }

Then implement the draftArt method in ArticleService, then define the draftArt method in ArticleMapper and write the sql statement in the corresponding xml file

public int publishArt(Integer aid,Integer state) {
    
    
        return articleMapper.publishArt(aid,state);
    }
 public int publishArt(@Param("aid") Integer aid,@Param("state") Integer state);
<update id="publishArt">
          update articleinfo set state=#{
    
    state} where id=#{
    
    aid}
     </update>

Implement blog home page (blog_list.html)

insert image description here
The blog homepage stores all blogs, and there are 5 buttons, namely, view full text, home page, previous page, next page and last page

The first page, the previous page, the next page and the last page belong to the paging function. Next, let’s talk about the idea of ​​paging and
the elements of paging:

  1. Page number (pageIndex): the page of data to be queried
  2. How many pieces of data are displayed on each page (pageSize): the maximum length of data displayed on each page.
    First, all data is queried from the database. There is a keyword limit in the query statement, so that you can limit how many records are sent to the front end. For example, limit pageSize, query pageSize records, then send them to the front end and render the page
    database. Another keyword is offset (offset). For example, limit 2 offer 2 means skipping the first two records, and then querying the first two records. 3, the 4th record.
    So for example, if you want to query the data of the pageIndex page, then its offset is pageSize*(pageIndex-1),
    so the paging formula (offset): pageSize*(pageIndex-1)
    paging syntax: select * from articleinfo limit pageSize offset pageSize * (pageIndex-1)

Implement the front-end code

insert image description here

 var descLen = 200;//简介最大长度
    //字符串截取,将文章正文截取成简介
    function mySubstr(content) {
    
    
        if(content.length>descLen) {
    
    
            return content.substr(0,descLen);
        }
        return content;
    }
    var pindex = 1;//当前的页码
    var psize = 2;//每页显示的条数信息
    var totalpage = 1;//总页数

    //初始化分页参数,尝试从url中获取pindex和psize
    function initPageParam() {
    
    
        var pi = getURLParam("pindex");
        if(pi!="") {
    
    
            pindex=pi;
        }
        var ps = getURLParam("psize");
        if(ps!="") {
    
    
            psize=ps;
        }
    }
    initPageParam();

    //查询分页数据
    function getList() {
    
    
        jQuery.ajax({
    
    
            url:"/art/list",
            type:"GET",
            data:{
    
    
                "pindex":pindex,
                "psize":psize
            },
            success:function(result) {
    
    
                if(result.code==200 && result.data!=null && result.data.length>0) {
    
    
                    //循环拼接控制document
                    var finalHtml = "";
                    for(var i=0;i<result.data.length;i++) {
    
    
                        var item = result.data[i];
                        //如果state==2说明是草稿箱的文章,不显示出来
                        if(item.state==2) {
    
    
                            continue;
                        }
                        finalHtml+='<div class="blog">';
                        finalHtml+='<div class="title">'+item.title+'</div>';
                        finalHtml+='<div class="date">'+item.createtime+'</div>';
                        finalHtml+='<div class="desc">'+mySubstr(item.content)+'</div>';
                        finalHtml+='<a href="blog_content.html?id='+item.id+'" class="detail">查看全文</a>';
                        finalHtml+='</div>';
                    }
                    jQuery("#listDiv").html(finalHtml);
                }
            }
        });
    }
    getList();

    //查询总共多少页
    function getTotalPage() {
    
    
        jQuery.ajax({
    
    
            url:"/art/totalpage",
            type:"GET",
            data:{
    
    
                "psize":psize
            },
            success:function(result) {
    
    
                if(result.code==200 && result.data!=null) {
    
    
                    totalpage=result.data;
                }
            }
        });
    }
    getTotalPage();

    //首页
    function firstClick() {
    
    
        location.href="blog_list.html";
    }

    //上一页
    function beforeClick() {
    
    
        if(pindex<=1) {
    
    
            alert("前面已经没有内容了!");
            return false;
        }
        pindex-=1;
        location.href="blog_list.html?pindex="+pindex+"&psize="+psize;
    }

    //下一页
    function nextClick() {
    
    
        pindex = parseInt(pindex);
        if(pindex>=totalpage) {
    
    
            //已经是最后一页
            alert("后面已经没有内容了哦!");
            return false;
        }
        pindex+=1;
        location.href="blog_list.html?pindex="+pindex+"&psize="+psize;
    }

    //末页
    function lastClick() {
    
    
        pindex=totalpage;
        location.href="blog_list.html?pindex="+pindex+"&psize="+psize;
    }

Implement the backend code

The above involves two ajax requests, and
the urls of both requests need to be released
insert image description here

Let's first process the ajax request of the getTotalPage method

@RequestMapping("/totalpage")
    public Integer getTotalCount(Integer psize) {
    
    
        if(psize!=null) {
    
    
            //参数有效
            int totalCount = articleService.getTotalCount();
            int totalPage = (int)Math.ceil(totalCount*1.0/psize);
            return totalPage;
        }
        return null;
    }

Next, implement the getTotalCount method in ArticleService, then define the getTotalCount method in ArticleMapper and write the sql statement in the corresponding xml file

public int getTotalCount() {
    
    
        return articleMapper.getTotalCount();
    }
public int getTotalCount();
<select id="getTotalCount" resultType="java.lang.Integer">
          select count(*) from articleinfo where state=1
     </select>

Next, process the ajax request of getList

public List<ArticleInfo> getList(Integer psize,Integer offset) {
    
    
        return articleMapper.getList(psize,offset);
    }

Then implement getList in ArticleService, define getList in ArticleMapper and write sql statements in the corresponding xml file

 public List<ArticleInfo> getList(@Param("psize") Integer psize,@Param("offset") Integer offset);
 <select id="getList" resultType="com.example.demo.model.ArticleInfo">
          select * from articleinfo limit #{
    
    psize} offset #{
    
    offset}
     </select>

Persist session to redis

First of all, you need to install redis on your linux environment. I won’t introduce how to install it here,
and then configure the connection to redis on the application.properties of your project. Springboot has built-in session persistence to redis, so you only need to It needs to be configured on application.properties

spring.redis.host=#你redis安装在哪台机器的ip地址
spring.redis.password=#redis的密码
spring.redis.port=6379#redis默认端口号
spring.redis.database=0#redis操作的数据库
spring.session.store-type=redis
server.servlet.session.timeout=1800#session存到redis的过期时间
spring.session.redis.flush-mode=on_save#会将session保存到redis本地
spring.session.redis.namespace=spring:session

Other extensions

  • Scheduled release function
  • change avatar
  • Freeze the account
    , etc., you can try to implement it yourself, the source code link is below, and the source code will be updated when there are new functions in the future

Source link: https://gitee.com/maze-white/project/tree/master/blog_system_ssm

Guess you like

Origin blog.csdn.net/HBINen/article/details/129024004