Java项目---博客系统

news2024/11/18 1:39:41

博客系统url : 链接

项目已上传gitee : 链接

前言

之前笔者已经使用Servlet结合MySQL实现了第一版的个人博客。在这一版的博客系统中,将进行以下功能的升级:

  1. 框架升级:SSM版本,即(Spring + SpringMVC + MyBatis) ,结合MySQL、Redis以及JQuery。
  2. 密码升级:明文存储/md5存储—>加盐处理。
  3. 用户登录状态持久化升级:将session持久化到Redis/MySQL。
  4. 功能升级:实现分页功能。
  5. 使用拦截器升级用户登录验证。

一:新建项目

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

配置applicaiton.yml文件:

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

二:搭建项目框架

在这里插入图片描述

三:引入前端的页面

前端页面和上一版博客系统差别不大,此处直接引入即可。

在这里插入图片描述

置于static目录下即可。

四:实现前后端交互功能

4.1 统一数据格式返回

在这里插入图片描述

AjaxResult

package com.example.demo.common;

import java.util.HashMap;

/**
 * 自定义的统一返回对象
 */
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;
    }


    public static HashMap<String,Object> success(String msg, Object data) {
        HashMap<String,Object> result = new HashMap<>();
        result.put("code",200);
        result.put("msg",msg);
        result.put("data",data);
        return result;
    }

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



}

ExceptionAdvice

package com.example.demo.common;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * 异常类的统一处理
 */
@ControllerAdvice // 控制器通知类
@ResponseBody
public class ExceptionAdvice {

    @ExceptionHandler(Exception.class) // 异常处理器
    public Object exceptionAdvice(Exception e) {
        return AjaxResult.fail(e.getMessage(),-1);
    }
}

ResponseAdvice

package com.example.demo.common;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.util.HashMap;

/**
 * 统一数据返回封装
 */
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }


    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if(body instanceof HashMap) {// 已经是封装好的对象
            return body;
        }
        if(body instanceof String) {// 返回对象是String类型(特殊)
            ObjectMapper objectMapper = new ObjectMapper();
            try {
                return objectMapper.writeValueAsString(AjaxResult.success(body));
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        return AjaxResult.success(body);
    }
}

4.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">

</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="blog_edit.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">提交</button>
            </div>
        </div>
    </div>
</body>

</html>
Step1:引入jQuery

jQuery教程

在这里插入图片描述

Step2:编写前端代码
  <button id="submit" onclick="mysub()">提交</button>

<script>
    function mysub() {
        //1.非空校验
        var username = jQuery("#username");
        var password = jQuery("#password");
        var password2 = jQuery("#password2");
        if(username.val() == "") {
            alert("请输入用户名!");
            username.focus(); // 将光标移动到username的输入框处
            return false;
        }
        if(password.val() == "") {
            alert("请输入密码!");
            password.focus(); // 将光标移动到password的输入框处
            return false;
        }
        if(password2.val() == "") {
            alert("请再次确认密码!");
            password2.focus(); // 将光标移动到password2的输入框处
            return false;
        }
        if(password.val() != password2.val()) {
            alert("两次密码输入不一致,请重新输入!");
            password.focus();
            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 {
                        alert("注册失败,请稍后再试!");
                      }
            }
        });
    }
</script>

在这里插入图片描述

Step3:编写后端代码

UserController

package com.example.demo.controller;

import com.example.demo.common.AjaxResult;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 用户控制器
 */
@RestController// 返回页面不是数据
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping("/reg")
    public Object reg(String username,String password) {
        //1.非空校验
        if(!StringUtils.hasLength(username) || !StringUtils.hasLength(password)) {
            return AjaxResult.fail(-1,"非法的参数请求");
        }
        //2.进行添加操作
        int result = userService.add(username,password);
        if(result == 1) {
            return AjaxResult.success("添加成功!",1);
        } else {
            return AjaxResult.fail("数据库添加出错!",-1);
        }
    }
}

UserService

package com.example.demo.service;

import com.example.demo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 用户表服务层
 */
@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

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

UserMapper

package com.example.demo.mapper;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
 * 用户表mapper
 */
@Mapper
public interface UserMapper {

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

UserMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
<insert id = "add">
    insert into userinfo(username,password)
    values(#{username},#{password})
</insert>
</mapper>

查看数据库中的数据:
在这里插入图片描述

进行添加操作:

点击提交:
在这里插入图片描述

点击确认:

在这里插入图片描述

点击确认:

在这里插入图片描述

转到登录页。

4.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">
</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="blog_edit.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">
                <button id="submit">提交</button>
            </div>
        </div>
    </div>
</body>

</html>
Step1:引入jQuery
<script src="js/jquery.min.js"></script>
Step2:编写前端代码
<button id="submit" onclick="mysub()">提交</button>

<script>
    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.发送请求给后端
        jQuery.ajax({
            url:"/user/login",
            type:"POST",
            data:{
                "username":username.val(),
                "password":password.val()
            },
            success:function(result){
                if(result.code==200 && result.data==1){
                  alert("登录成功!");
                  location.href = "myblog_list.html";     
                }else{
                    alert("用户名或密码错误,请重新输入!");
                    username.focus();
                }
            }
        });
    }
</script>
Step3:编写后端代码

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.login(username, password);
        if (userInfo == null || userInfo.getId() <= 0) {
            // 用户名或密码错误,userInfo无效
            return -1;
        } else {
            //用户名和密码正确,将userInfo保存到session中
            HttpSession session = request.getSession();
            session.setAttribute(Constant.SESSION_USERINFO_KEY, userInfo);
            return 1;
        }
    }

UserService

    public UserInfo login(String username, String password) {
        return userMapper.login(username, password);
    }

UserMapper

    public UserInfo login(@Param("username") String username,
                          @Param("password") String password);

UserMapper.xml

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

进行登录操作:

在这里插入图片描述
在这里插入图片描述

点击确认,跳转到列表详情页。

在这里插入图片描述

4.4 统一用户登录权限验证

1.创建自定义拦截器,实现 HandlerInterceptor 接口的 preHandle(执行具体方法之前的预处理)⽅法。

LoginInterceptor

package com.example.demo.common;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

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

@Component
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(Constant.SESSION_USERINFO_KEY) != null) {
            // 当前用户已登录
            return true;
        }
        response.setStatus(401);// 未登录
        return false;
    }
}

Q : 为什么session中的key值都一样 , 却能够区分不同的登录用户身份呢 ?

A :
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

session!=null意味着sessionId不为空 , 而sessionId不为空 , 只能说明该客户端有过登录行为 , 而其session是否还有效 , 就要根据session.getAttribute()!=null来判断了.

2.将自定义拦截器加入 WebMvcConfigurer 的 addInterceptors 方法中。

package com.example.demo.common;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.ArrayList;
import java.util.List;

/**
 * 自定义拦截规则
 */
@Configuration
public class AppConfig implements WebMvcConfigurer {

    //不拦截的url集合
    List<String> excludes = new ArrayList<String>(){{
        add("/js/**");// "/js/**"表示放行js路径下的所有文件
        add("/editor.md/**");
        add("/css/**");
        add("/img/**");
        add("/user/login");// 放行登录接口
        add("/user/reg");// 放行注册接口
        add("/art/setrcount");//  放行访问量设置接口
        add("/art/list");//  放行文章分页列表
        add("/art/totalpage");// 放行总页面数接口
        add("/login.html");
        add("/blog_list.html");
        add("/myblog_list.html");
        add("/reg.html");
    }
    };

    @Autowired
    private LoginInterceptor loginInterceptor;// 导入拦截器

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

4.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_list.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="blog_edit.html">写博客</a>
        <a href="javascript:onExit()">注销</a>
    </div>
    <!-- 版心 -->
    <div class="container">
        <!-- 左侧个人信息 -->
        <div class="container-left" >
            <div class="card">
                <img src="img/touxiang.jpg" class="avtar" alt="">
                <h3 id="username"></h3>
                <a href="https://gitee.com/">gitee 地址</a>
                <div class="counter">
                    <span>文章</span>
                    <span>访问量</span>
                </div>
                <div class="counter">
                    <span id="articleCount">0</span>
                    <span id="totalRcount">0</span>
                </div>
            </div>
        </div>
        <!-- 右侧内容详情 -->
        <div class="container-right" id="artlistDiv">
            
        </div>
    </div>
</body>
</html>
Step1.引入jQuery
 <script src="js/jquery.min.js"></script>
Step2:编写前后端代码

4.5.1 注销功能

在这里插入图片描述

在这里插入图片描述

4.5.2 显示所有文章

显示文章 , 只需将原来页面中的文章div用数据库中查询出的数据进行替换即可 . 实现步骤如下 :

myblog_list.html


  var descLength = 80; // 简介最大长度
    // 字符串截取,将文章正文截取成简介
    function mySubstr(content){
        if(content.length>descLength){
            return content.substr(0,descLength);
        }
        return content;
    }
    
    // 初始化个人列表信息
    function initList(){
        jQuery.ajax({
                url:"/art/mylist",
                type:"POST",
                data:{},
                success:function(result){
                    getArticleCount(result.data[0].uid); //获取文章数
                    getTotalRcount(result.data[0].uid);  //获取访问量
                    if(result.code==200 && result.data!=null 
                    && result.data.length>0){
                        // 此人发表文章了
                        var html="";
                        result.data.forEach(function(item){
                            html+='<div class="blog">';
                            html+='<div class="title">'+item.title+'</div>';
                            html+='<div class="date">'+item.createtime+'</div>'
                            html+='<div class="desc">'+mySubstr(item.content)+' </div>';
                            html+='<div style="text-align: center;margin-top: 50px;">';
                            html+='<a id="clickIt" href="blog_content.html?id='+item.id+'">查看详情</a>&nbsp;&nbsp;';
                            html+='<a id="clickIt" href="blog_update.html?id='+item.id+'">修改</a>&nbsp;&nbsp;'
                            html+='<a id="clickIt" href="javascript:myDel('+item.id+')">删除</a></div>' +'</div>';  
                        }); 
                        jQuery("#artlistDiv").html(html);     
                    }else{
                        // 此人未发表任何文章
                        jQuery("#artlistDiv").html("<h1>暂无数据</h1>");
                    }
                },
                error:function(err){
                    if(err!=null && err.status==401){
                        alert("用户未登录,即将跳转到登录页!");
                        // 已经被拦截器拦截了,未登录
                        location.href = "/login.html";
                    }
                }
            });
    }
    initList(); // 当浏览器渲染引擎执行到此行的时候就会调用 initList() 方法

后端部分 :

ArticleController

    /**
     * 返回文章信息
     * @param request
     * @return
     */
    @RequestMapping("mylist")
    public List<ArticleInfo> myList(HttpServletRequest request) {
        UserInfo userInfo = SessionUtil.getLoginUser(request);
        if(userInfo != null) {
            return articleService.getMyList(userInfo.getId());
        }
        return null;
    }

SessionUtil

package com.example.demo.common;
import com.example.demo.model.UserInfo;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

public class SessionUtil {
    /**
     * 查询当前登录用户的session
     * @param request
     * @return
     */
    public static UserInfo getLoginUser(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session != null &&
                session.getAttribute(Constant.SESSION_USERINFO_KEY) != null) {
            return (UserInfo) session.getAttribute(Constant.SESSION_USERINFO_KEY);
        }
        return null;
    }

}

ArticleService

package com.example.demo.service;
import com.example.demo.mapper.ArticleMapper;
import com.example.demo.model.ArticleInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;

/**
 *文章表服务层
 */
@Service
public class ArticleService {

    @Autowired
    private ArticleMapper articleMapper;

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

ArticleMapper

package com.example.demo.mapper;
import com.example.demo.model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;

/**
 * 文章表mapper
 */
@Mapper
public interface ArticleMapper {
        public List<ArticleInfo> getMyList(@Param("uid") Integer uid);
}

ArticleMapper.xml

<select id="getMyList" resultType="com.example.demo.model.ArticleInfo">
    select * from articleinfo where uid=#{uid}
</select>

运行结果 :

在这里插入图片描述

因为进行了统一的数据格式返回封装 , 所以返回的格式非常清晰 , 当状态码code为200时 , 证明后端成功查询并返回了数据 , 此时前端需要做的 , 就是将后端返回的数据显示在页面上 .

在获取全部文章并显示在页面上同时 , 需要将该作者的文章数和访问量获取到 , 并显示在个人信息栏 . 其代码分别如下 :

前端代码 :

        //获取文章数
    function getArticleCount(uid) {
            var articleCount = 0;
            jQuery.ajax({
                    url:"/art/getarticlecount",
                    type:"POST",
                    data:{"uid":uid},
                    success:function(result) {
                        if(result.code == 200 && result.data != null) {
                            jQuery("#articleCount").text(result.data);
                        }
                    },
                    error:function(err) {

                    }
                });
        }

         //获取作者总访问量
    function getTotalRcount(uid) {
            jQuery.ajax({
                url:"/art/gettotalrcount",
                type:"POST",
                data:{"uid":uid},
                success:function(result) {
                    jQuery("#totalRcount").text(result.data);
                }
            });
        }

后端代码 :

ArticleController

 /**
     * 获取总访问量
     * @return
     */
    @RequestMapping("/gettotalrcount")
    public Integer getTotalRcount(Integer uid) {
        return articleService.getTotalRcount(uid);
    }
    /**
     * 获取文章数
     * @param uid
     * @return
     */
    @RequestMapping("/getarticlecount")
    public Integer getArticleCount(Integer uid) {
        return articleService.getArticleCount(uid);
    }

ArticleService

    /**
     * 获取总访问量
     * @return
     */
    public Integer getTotalRcount(Integer uid) {
        return articleMapper.getTotalRcount(uid);
    }
    /**
     * 获取文章数
     * @param id
     * @return
     */
    public Integer getArticleCount(Integer id) {
        return articleMapper.getArticleCount(id);
    }

ArticleMapper

        //获取当前登录用户文章数
        public Integer getArticleCount(@Param("uid") Integer uid);

        //获取总访问量
        public Integer getTotalRcount(@Param("uid") Integer uid);

ArticleMapper.xml

<!--    获取当前登录用户文章数-->
    <select id="getArticleCount" resultType="java.lang.Integer">
        select count(*) from articleinfo where uid=#{uid}
    </select>

<!--    获取总访问量-->
<select id="getTotalRcount" resultType="java.lang.Integer">
    select sum(rcount) from articleinfo where uid=#{uid};
</select>

4.5.3 初始化侧边栏

在第二步中 , 已经获取到了当前用户的文章数和访问量 , 只需获取到用户名并加载到页面上即可 .
前端代码 :

    // 获取个人信息
    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){
                    
                }
            });
    }
    myInfo();

后端代码 :

UserController

    /**
     * 获取当前登录用户列表页
     * @param request
     * @return
     */
    @RequestMapping("/myinfo")
    public UserInfo myInfo(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session != null &&
                session.getAttribute(Constant.SESSION_USERINFO_KEY) != null) {
            return (UserInfo) session.getAttribute(Constant.SESSION_USERINFO_KEY);
        }
        return null;
    }

4.5.4 编辑页

点击写博客 , 跳转至博客编辑页 , 编写编辑页代码 .

在这里插入图片描述

关键操作在于点击"发布文章" , 前端将标题和正文返回给后端 , 后端将该篇文章存储到数据库中 .

前端代码 :

<!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="blog_edit.html">写博客</a> -->
        <a href="login.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(){
            var title = jQuery("#title");
            var content = editor.getValue();
            // 非空效验
            if(title.val()==""){
                title.focus();
                alert("请先输入标题!");
                return false;
            }
            if(content==""){
                content.focus();
                alert("请先输入正文!");
                return false;
            }
            jQuery.ajax({
                url:"/art/edit",
                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";
                    }
                }
            });

        }
    </script>
</body>

</html>

后端代码 :

ArticleController

    //编辑文章
    @RequestMapping("/edit")
    public int edit(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) {
            return articleService.edit(title, content,userInfo.getId());
        }
        return 0;
    }

ArticleService

    //编辑文章
    public int edit(String title, String content,Integer uid) {
        return articleMapper.edit(title, content,uid);
    }

ArticleMapper

        //编辑文章
        public int edit(@Param("title") String title,
                        @Param("content") String content,
                        @Param("uid") Integer uid);

ArticleMapper.xml

<!--    编辑文章-->
    <insert id="edit" >
        insert into articleinfo(title,content,uid) values(#{title},#{content},#{uid})
    </insert>

4.5.5 主页[分页功能]

点击主页 , 跳转至博客列表主页 , 这个页面包括所有用户的文章 . 同时在该页面中 , 实现了分页功能 , 同一页面只显示2篇文章 . 分页功能的原理如下 :
在这里插入图片描述

删除原有文章 , 插入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/list.css">
    <link rel="stylesheet" href="css/blog_list.css">
    <link rel="stylesheet" href="css/homepage.css">
    <script src="js/jquery.min.js"></script>
    <script src="js/tools.js"></script>
</head>

<body>
    <!-- 导航栏 -->
    <div class="nav">
        <img src="img/logo2.jpg" alt="">
        <span class="title">博客系统</span>
        <!-- 用来占据中间位置 -->
        <span class="spacer"></span>
        <a href="myblog_list.html">个人中心</a>
        <a href="login.html">登录</a>
        <a href="reg.html">注册</a>
        <a href="javascript:onExit()">注销</a>
    </div>
    <!-- 版心 -->
    <div class="container">
        <!-- 右侧内容详情 -->
        <div class="container-right" style="width: 100%;">
            <div id="listDiv">
                
            </div>
            
            <hr>
            <div class="blog-pagnation-wrapper">
                <button class="blog-pagnation-item" onclick="firstClick()">首页</button> 
                <button class="blog-pagnation-item" onclick="beforeClick()">上一页</button>  
                <button class="blog-pagnation-item" onclick="nextClick()">下一页</button>
                <button class="blog-pagnation-item" onclick="lastClick()">末页</button>
            </div>
        </div>
    </div>
    <script>

    var descLength = 80; // 简介最大长度
    // 字符串截取,将文章正文截取成简介
    function mySubstr(content){
        if(content.length>descLength){
            return content.substr(0,descLength);
        }
        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 pz = getUrlParam("psize");     
           if(pz!=""){
            psize=pz;
           }
        }
        initPageParam();

        // 查询总共有多少页的数据
        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 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];
                            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+='<div style="text-align: center;margin-top: 50px;">';
                            finalHtml+='<p style="text-align: center">';
                            finalHtml+='<a id="clickIt" href="blog_content.html?id='+item.id+'">查看全文</a></p></div>';
                            finalHtml+='</div>';
                        }
                        jQuery("#listDiv").html(finalHtml);
                    }
                }
            });
        }
        getList();
    
        // 首页
        function firstClick(){
            location.href = "blog_list.html";
        }

        // 上一页
        function beforeClick(){
            if(pindex<=1){
                //alert("当前已是第一页!");
                location.reload();
                return false;
            }
            pindex = parseInt(pindex)-1;
            location.href = "blog_list.html?pindex="+pindex+"&psize="+psize;
        }

        // 下一页
        function nextClick(){
            pindex = parseInt(pindex)+1;
            if(pindex>totalpage){
                location.reload();
                // 已经在最后一页了
                //alert("当前已是最后一页!");
                return false;
            }
            location.href = "blog_list.html?pindex="+pindex+"&psize="+psize;
        }

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

    // 退出登录
    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";
                    }
                }
            });
        }
    }

    </script>
</body>
</html>

后端代码 :

ArticleController

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

    //获取分页
    @RequestMapping("/list")
    public List<ArticleInfo> getList(Integer pindex, Integer psize) {
        if (pindex == null || psize == null) {
            return null;
        }
        // 分页公式,计算偏移量
        int offset = (pindex - 1) * psize;
        return articleService.getList(psize, offset);
    }

ArticleService

    //获取总页数
    public int getTotalCount() {
        return articleMapper.getTotalCount();
    }
    
    //获取分页
    public List<ArticleInfo> getList(Integer psize, Integer offset) {
        return articleMapper.getList(psize, offset);
    }

ArticleMapper

        //获取总页数
        public int getTotalCount();
        
        //获取分页
        public List<ArticleInfo> getList(@Param("psize") Integer psize,
                                         @Param("offset") Integer offset);

ArticleController.xml

    <!--    获取总页数-->
    <select id="getTotalCount" resultType="java.lang.Integer">
        select count(*) from articleinfo
    </select>
    
    <!--    获取分页-->
    <select id="getList" resultType="com.example.demo.model.ArticleInfo">
        select * from articleinfo limit #{psize} offset #{offset}
    </select>

点击首页 :

在这里插入图片描述

点击下一页 :

在这里插入图片描述

点击末页 :

在这里插入图片描述

4.5.6 查看详情

前端代码 :

前端只需发送一个ajax请求 , 携带文章id , 后端在数据库中查询出文章标题和正文 , 返回给前端即可 . 每次查看该篇文章 , 我们需要将访问量 + 1 ,所以还需发送一个ajax请求 , 用于设置访问量 + 1 . 同理 , 在显示所有人文章的主页 , 点击查看全文 , 我们也应该设置访问量 + 1 . 综上所述 , 只要访问blog_content页面 , 就使访问量 + 1 .

//获取文章详细信息
 function getArticleDetail() {
     if(aid != null && aid > 0) {
         //访问后端,更新访问量
         jQuery.ajax({
             url:"/art/setrcount",
             type:"POST",
             data:{"aid":aid},
             success:function(result) {
                 
             }
         });

         //访问后端查询文章详情
         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
                     });
                     myInfo(art.uid);
                     getArticleCount(art.uid);
                     getTotalRcount(art.uid);//侧边栏访问量信息
                 }
             }
         });

     }
 }
 getArticleDetail();

ArticleController

    //设置访问量
    @RequestMapping("/setrcount")
    public int setRcount(Integer aid) {
        return articleService.setRcount(aid);
    }
    
    //blog_content页面获取文章详情
    @RequestMapping("/detail")
    public Object getDetail(Integer aid) {
        if(aid != null && aid > 0) {
            return AjaxResult.success(articleService.getDetail(aid));
        }
        return AjaxResult.fail(-1,"查询失败");
    }

ArticleService

    //设置访问量
    public int setRcount(Integer aid) {
        return articleMapper.setRcount(aid);
    }
    
    //获取文章内容
    public ArticleInfo getDetail(Integer aid) {
        return articleMapper.getDetail(aid);
    }

ArticleMapper

        //设置访问量
        public int setRcount(@Param("aid") Integer aid);
        
        //获取文章内容
        public ArticleInfo getDetail(@Param("aid") Integer aid);

ArticleController.xml

<!--    设置访问量-->
    <update id="setRcount">
        update articleinfo set rcount=rcount+1 where id=#{aid}
    </update>
    
<!--    根据文章编号查询文章信息-->
    <select id="getDetail" resultType="com.example.demo.model.ArticleInfo">
        select * from articleinfo where id=#{aid}
    </select>

4.5.7 修改文章

当我们点击修改时 , 首先要显示整篇文章 , 这需要前端向后端请求数据 , 并且通过ajax请求传递当前文章的id . 其次 , 发送一个更新文章的ajax请求 , 这可以完全参考文章编辑页的做法 . 代码如下 :

前端代码 :

<!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/tools.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="blog_edit.html">写博客</a>
        <a href="javascript:onExit()">注销</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 // 
            });
        }

// 提交
function mysub(){
            var title = jQuery("#title");
            var content = editor.getValue();
            // 非空效验
            if(title.val()==""){
                title.focus();
                alert("请先输入标题!");
                return false;
            }
            if(content==""){
                title.focus();
                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("查询失败,请重试!");
                        }
                    },
                    error:function(err){
                        if(err!=null && err.status==401){
                            alert("用户未登录,即将跳转到登录页!");
                            // 已经被拦截器拦截了,未登录
                            location.href = "/login.html";
                        }
                    }
                });
        }
    }
    showArt();

    // 退出登录
    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";
                    }
                }
            });
        }
    }

    </script>
</body>

</html>

后端代码 :

ArticleController

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

ArticleService

    //更新文章
    public int update(Integer aid, Integer uid, String title, String content) {
        return articleMapper.update(aid, uid, title, content);
    }

ArticleMapper

        //更新文章
        public int update(@Param("aid") Integer aid,
                          @Param("uid") Integer uid,
                          @Param("title") String title,
                          @Param("content") String content);

ArticleController.xml

<!--    修改文章-->
    <update id="update">
        update articleinfo set title=#{title},content=#{content}
        where id=#{aid} and uid=#{uid}
    </update>

4.5.8 删除文章

删除文章 , 直接将该篇文章从数据库中删除即可 , 前端只需向后端传递表示该篇文章的唯一参数 —> 文章id .

前端代码 :

   //删除文章
   function myDel(id){
        if(confirm("确认要删除该文章吗?")){
            jQuery.ajax({
                url:"/art/mydel",
                type:"POST",
                data:{
                    "id" : id
                },
                success:function(result) {
                    if(result.code==200 && result.data!=null) {
                        alert("删除成功!");
                        location.href = "myblog_list.html";
                    }
                },
                error:function(err){
                    if(err != null) {
                        alert("删除失败,请重试!");
                    }
                }
            });
        }
    }

后端代码 :

ArticleController

    //删除文章
    @RequestMapping("/mydel")
    public boolean delete(Integer id) {
        if(id == null) {
            return false;
        }
        return articleService.delete(id);
    }

ArticleService

    //删除文章
    public boolean delete(Integer id) {
        return articleMapper.delete(id);
    }

ArticleMapper

        //删除文章
        public boolean delete(@Param("id") Integer id);

ArticleController.xml

<!--    删除文章-->
    <delete id="delete">
        delete from articleinfo where id=#{id}
    </delete>

五 : 密码加盐

存储密码的方式 , 主要有以下几种 :

1.明文 , 显然明文存储是最不安全的 ;

2.MD5加密 , 即MD5消息摘要算法,属Hash算法一类。MD5算法对输入任意长度的消息进行运行,产生一个128位的消息摘要(32位的数字字母混合码)。

MD5主要特点 : 不可逆,相同数据的MD5值肯定一样,不同数据的MD5值不一样 . 那这个时候 , 我如果搞一个对照表 , 就可以进行暴力破解了 , 比如 :

在这里插入图片描述

我们对"123"这个字符串加密两次 , 发现加密结果是一致的 . 在任何时间 , 任何地点 , 对"123"字符串的MD5加密都是这个结果 . 那么 , 如果我有一张对照表 , key值是MD5加密结果 , value值是原字符串 , 我就可以通过遍历的方式通过key拿value . 所以MD5仅仅提供了最基础的加密功能 .

3.加盐算法 , 每次在进行加密时 , 给该密码加一个盐值 , 并且每次生成的盐值都不同 .

在这里插入图片描述

代码如下 :

package com.example.demo.common;

import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;

import java.util.UUID;

/**
 * 加盐加密类
 */
public class SecurityUtil {

    //加盐加密
    public static String encrypt(String password) {
        //1.每次生成32位的不同盐值
        String salt = UUID.randomUUID().toString().replace("-","");
        //2.盐值+密码生成最终的32位密码
        String finalPassword = DigestUtils.md5DigestAsHex((salt + password).getBytes());
        //3.同时返回盐值+最终密码
        return salt + finalPassword;
    }

    //密码验证
    public static boolean decrypt(String password,String databasePassword) {
        //1.非空校验
        if(!StringUtils.hasLength(password) || !StringUtils.hasLength(databasePassword)) {
            return false;
        }
        //2.验证数据库存储密码是否为64位
        if(databasePassword.length() != 64) {
            return false;
        }
        //3.提取盐值
        String salt = databasePassword.substring(0,32);
        //4.生成待验证密码
        String securityPassword = DigestUtils.md5DigestAsHex((salt+password).getBytes());
        //5.返回密码验证的结果
        return (salt + securityPassword).equals(databasePassword);
    }
}

六 : 部署

6.1 建库建表

连接数据库 :

mysql -uroot

建立数据库 :

create database myblog;

建表 :

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

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

注意 : 此处为简便处理 , 将创建文章时间和更新文章时间合成了一个字段 , 即createtime .

6.2 打包

首先修改配置信息 , linux上数据库密码默认为空 .

在这里插入图片描述

设置打包后的包名 :

在这里插入图片描述

双击package进行打包 :

在这里插入图片描述

将jar包拷贝到云服务器上 ;

在这里插入图片描述

6.3 运行

nohup java -jar myblog.jar &

博客系统url : 链接

本文到此结束 !

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

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

相关文章

@Import注解的原理

此注解是springboot自动注入的关键注解&#xff0c;所以拿出来单独分析一下。 启动类的run方法跟进去最终找到refresh方法&#xff1b; 这里直接看这个org.springframework.context.support.AbstractApplicationContext#refresh方法即可&#xff0c;它下面有一个方法 invoke…

Linux基础命令-fdisk管理磁盘分区表

文章目录 fdisk 命令介绍 命令格式 基本参数 1&#xff09;常用参数 2&#xff09;fdisk菜单操作说明 创建一个磁盘分区 1&#xff09;创建分区 2&#xff09;创建交换分区 参考实例 1&#xff09; 显示当前分区的信息 2&#xff09; 显示每个磁盘的分区信息 命令…

关于单目标约束优化问题的讲解及实现过程

一、前沿 优化问题一直是工程领域、路径规划领域等绕不开的话题,而真正的实际问题不是只是单目标优化问题,而是涉及到高维度且带多约束的问题,其中约束包含等式约束、不等式约束或者二者都有,这给优化研究提高了难度。 在中学的时候,应该都遇到过线性规划问题,类似于如…

LeetCode 热题 C++ 200. 岛屿数量 206. 反转链表 207. 课程表 208. 实现 Trie (前缀树)

LeetCode200 给你一个由 1&#xff08;陆地&#xff09;和 0&#xff08;水&#xff09;组成的的二维网格&#xff0c;请你计算网格中岛屿的数量。 岛屿总是被水包围&#xff0c;并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 此外&#xff0c;你可以假设…

虹科新闻|虹科与iX systems正式建立合作伙伴关系

近日&#xff0c;虹科与美国iXsystems公司达成战略合作&#xff0c;虹科正式成为iXsystems公司在中国区域的认证授权代理商。未来&#xff0c;虹科将携手iXsystems&#xff0c;共同致力于提供企业级存储解决方案。虹科及iXsystems双方的高层领导人员都对彼此的合作有很大的信心…

【JVM】垃圾回收

6、垃圾回收机制 6.1、对象成为垃圾的判断依据 在堆空间和元空间中&#xff0c;GC这条守护线程会对这些空间开展垃圾回收⼯作&#xff0c;那么GC如何判断这些空间的对象是否是垃圾&#xff0c;有两种算法&#xff1a; 引用计数法&#xff1a; 对象被引用&#xff0c;则计数…

搜广推 NeuralCF - 改进协同过滤+矩阵分解的思想

😄 NeuralCF:2017新加坡国立大学提出。【后文简称NCF】 😄 PNN:2016年上海交通大学提出。 文章目录 NeuralCF动机原理general NCFNCF终极版(GMF+MLP的结合)缺点优点ReferenceNeuralCF 动机 前面学了MF,可知MF在用户-物品评分矩阵的基础上做矩阵分解(用户矩阵Q和物品…

Codeforces Round #851 (Div. 2)(A~D)

A. One and Two给出一个数组&#xff0c;该数组仅由1和2组成&#xff0c;问是否有最小的k使得k位置的前缀积和后缀积相等。思路&#xff1a;计算2个数的前缀和即可&#xff0c;遍历判断。AC Code&#xff1a;#include <bits/stdc.h>typedef long long ll; const int N 1…

Maxwell系列:Maxwell采集Mysql到Kafka

目录 Apache Hadoop生态-目录汇总-持续更新 1&#xff1a;直接命令行启动(开发环境使用) 1.1&#xff1a;创建topic&#xff08;可忽略&#xff0c;默认会自动创建&#xff09; 1.2&#xff1a;命令行方式启动maxwell采集通道 1.3&#xff1a;测试流程 2&#xff1a;通过配…

taobao.top.once.token.get( 网关一次性token获取 )

&#xffe5;开放平台免费API必须用户授权聚石塔内调用 网关一次性token获取&#xff0c;对接文档: 公共参数 HTTP地址:http://gw.api.taobao.com/router/rest 公共请求参数: 公共响应参数: 请求参数 响应参数 点击获取key和secret 请求示例 TaobaoClient client new Defa…

2023前端一面vue面试题合集

函数式组件优势和原理 函数组件的特点 函数式组件需要在声明组件是指定 functional:true不需要实例化&#xff0c;所以没有this,this通过render函数的第二个参数context来代替没有生命周期钩子函数&#xff0c;不能使用计算属性&#xff0c;watch不能通过$emit 对外暴露事件&…

【Kafka】生产者

客户端开发 生产者就是负责向kafka发送消息的应用程序。一个正常的生产逻辑的步骤为&#xff1a; 配置生产者客户端参数及创建相应的生产者实例。 构建待发送的消息。 发送消息。 关闭生产者实例。 在创建真正的生产者实例前需要配置相应的参数&#xff0c;比如需要连接的…

顺序表的构造及功能

定义顺序表是一种随机存储都结构&#xff0c;其特点是表中的元素的逻辑顺序与物理顺序相同。假设线性表L存储起始位置为L(A)&#xff0c;sizeof(ElemType)是每个数据元素所占的存储空间的大小&#xff0c;则线性表L所对应的顺序存储如下图。顺序表的优缺点优点&#xff1a;随机…

【面试题】 3 个加强理解TypeScript 的面试问题

TypeScript 作为 JavaScript 的超集&#xff0c;让 JavaScript 越来越像一门“正经的”语言。和纯粹的 JavaScript 语言相比&#xff0c;TypeScript 提供静态的类型检查&#xff0c;提供类的语法糖&#xff08;这一点是继承了 ES6 的实现&#xff09;&#xff0c;让程序更加规范…

Python实现贝叶斯优化器(Bayes_opt)优化LightGBM回归模型(LGBMRegressor算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。1.项目背景贝叶斯优化器 (BayesianOptimization) 是一种黑盒子优化器&#xff0c;用来寻找最优参数。贝叶斯优化器是…

测试用例篇

1.测试用例的意义 测试用例&#xff08;Test Case&#xff09;是为了实施测试而向被测试的系统提供的一组集合&#xff0c;这组集合包含&#xff1a;测试环境、操作步骤、测试数据、预期结果等要素。 测试用例的意义是为了帮助测试人员了解测什么&#xff0c;怎么测 eg&#x…

docker的使用方法

docker技术 同一个操作系统内跑多套不同版本依赖的业务 docker可以使同一个物理机中进程空间&#xff0c;网络空间&#xff0c;文件系统空间相互隔绝 虚拟机弊端&#xff1a;每个需要安装操作系统&#xff0c;太重量级&#xff0c;资源需要提前分配好 部署程序 开发环境 win…

WebRTC现状以及多人视频通话分析

1.WebRTC 概述WebRTC&#xff08;网页实时通信技术&#xff09;是一系列为了建立端到端文本或者随机数据的规范&#xff0c;标准&#xff0c;API和概念的统称。这些对等端通常是由两个浏览器组成&#xff0c;但是WebRTC也可以被用于在客户端和服务器之间建立通信连接&#xff0…

【源码解析】重试模板RetryTemplate源码分析

应用场景 日常开发中&#xff0c;经常会遇到这样的场景&#xff1a;执行一次接口调用&#xff0c;如RPC调用&#xff0c;偶现失败&#xff0c;原因可能是dubbo超时、连接数耗尽、http网络抖动等&#xff0c;出现异常时我们并不能立即知道原因并作出反应&#xff0c;可能只是一…

新冠小阳人症状记录

原想挺过春节后再养&#xff0c;发现事与愿违。生理期期间抵抗力下降&#xff0c;所以在生理期第二天就有些症状了。可能是生理期前一天出去采购食物染上&#xff0c;也可能是合租夫妻染上。anyway&#xff0c;记录下自己的症状与相应有效的偏方&#xff1a; 第一天&#xff1a…