Spring-博客系统项目

一,实现效果

登录:

注册:

博客列表

个人博客中心

博客详情:

更新博客

编写博客

二,数据库的建立和连接

首先,需要建库,需要两个实体,一个是用户,一个是博客,需要如下属性,需要注意的是需要将密码的变长字符创设置的长一些,因为之后会对用户的密码进行加密,该博客中密码要64为.所以提前预留好空间.另外由于用户和博客都是不是物理删除,而是逻辑删除,所以每一个实体都有delete_flag

sql 复制代码
-- 建表SQL
create database if not exists java_blog charset utf8mb4;

use java_blog;
-- 用户表
DROP TABLE IF EXISTS java_blog.user_info;
CREATE TABLE java_blog.user_info(
        `id` INT NOT NULL AUTO_INCREMENT,
        `user_name` VARCHAR ( 128 ) NOT NULL,
        `password` VARCHAR ( 128 ) NOT NULL,
        `github_url` VARCHAR ( 128 ) NULL,
        `delete_flag` TINYINT ( 4 ) NULL DEFAULT 0,
        `create_time` DATETIME DEFAULT now(),
        `update_time` DATETIME DEFAULT now() ON UPDATE now(),
        PRIMARY KEY ( id ),
UNIQUE INDEX user_name_UNIQUE ( user_name ASC )) ENGINE = INNODB DEFAULT CHARACTER 
SET = utf8mb4 COMMENT = '用户表';

-- 博客表
drop table if exists java_blog.blog_info;
CREATE TABLE java_blog.blog_info (
  `id` INT NOT NULL AUTO_INCREMENT,
  `title` VARCHAR(200) NULL,
  `content` TEXT NULL,
  `user_id` INT(11) NULL,
  `delete_flag` TINYINT(4) NULL DEFAULT 0,
  `create_time` DATETIME DEFAULT now(),
  `update_time` DATETIME DEFAULT now() ON UPDATE now(),
  PRIMARY KEY (id))
ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '博客表';

-- 新增用户信息
insert into java_blog.user_info (user_name, password,github_url)values("zhangsan","123456","https://gitee.com/bubble-fish666/class-java45");
insert into java_blog.user_info (user_name, password,github_url)values("lisi","123456","https://gitee.com/bubble-fish666/class-java45");

insert into java_blog.blog_info (title,content,user_id) values("第一篇博客","111我是博客正文我是博客正文我是博客正文",1);
insert into java_blog.blog_info (title,content,user_id) values("第二篇博客","222我是博客正文我是博客正文我是博客正文",2);

进行数据库的配置,我们这里使用的是Mybatis-plus,所以需要引入Mybatis-plus依赖

XML 复制代码
<dependency><!--Mybatis-plus依赖-->
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
			<version>3.5.5</version>
		</dependency>

链接数据库,配置Resource->application.yml文件,这里要更改自己创建的库名,和自己数据库的密码,另外这里还配置了驼峰转化 (起到数据库中对象的属性和java创建的类属性相对应),数据库的日志打印,和日志永久化和分割

javascript 复制代码
#数据库连接配置
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/java_blog?characterEncoding=utf8&useSSL=false
    username: root
    password: "****"
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true  # 驼峰转化
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 配置mybatis-plus打印日志

logging:
  file:
    name: logger/spring-blog-log
  logback:   #日志分割
    rollingpolicy:
      file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%i
      max-file-size: 1MB

创建与数据库元素对应的java类,其中 @TableId(value = "id",type = IdType.AUTO),告诉 MyBatis-Plus 当前字段是数据库表的主键,指定数据库中主键列的列名(如果字段名与列名一致,可省略).定义主键的生成策略为数据库自增

java 复制代码
@Data
public class BlogInfo {
    @TableId(value = "id",type = IdType.AUTO)
    private Integer id;
    private String title;
    private String content;
    private Integer userId;
    private int deleteFlag;
    private Date createTime;
    private Date updateTime;

}
java 复制代码
@Data
public class UserInfo {
    @TableId(value = "id",type = IdType.AUTO)
    private Integer id;
    private String userName;
    private String password;
    private String githubUrl;
    private int deleteFlag;
    private Date createTime;
    private Date updateTime;
}

之后创建Mapper接口,使用Mybatis-plus来操作数据库,由于数据库里面有两个表,所以创建两个与之相对应的接口,记得加上@Mapper注解

java 复制代码
@Mapper
public interface BlogInfoMapper extends BaseMapper<BlogInfo> {
}
java 复制代码
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}

这个项目是基于Spring-Boot创建的,在创建Spring项目的时候,需要引入一些依赖,Spring Boot 提供的 Web 开发场景启动器,mysql的驱动,lombok依赖

XML 复制代码
        <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</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>

三, 统一结果

统一返回,在开发中通常需要统一格式返回,所以我们设置一个类,将每个方法返回的数据统一用这个类进行包装,命名为Result,里面包含成功的返回,以及发生异常的返回.这个类里面的属性为{code,errMsg,data}状态码,正常为200,异常为-1.errMsg为错误信息,data通常放正常返回的数据内容,状态码我们放到一个枚举类里面

java 复制代码
@Getter
@AllArgsConstructor
public enum ResultStatusEnum {
     SUCCESS(200),
     FILE(-1);
     private  int code;
}
java 复制代码
import com.ABdolphin.blog.common.enums.ResultStatusEnum;
import lombok.Data;

@Data
public class Result<T> {
    private int code;
    private String errMsg;
    private T data;

    public static <T> Result<T> ok (T data){
        Result<T> result=new Result();
        result.setCode(ResultStatusEnum.SUCCESS.getCode());
        result.setErrMsg(null);
        result.setData(data);
        return result;
    }
    public static <T> Result<T> fail (int code,String errMsg){
        Result<T> result=new Result();
        result.setCode(ResultStatusEnum.FILE.getCode());
        result.setErrMsg(errMsg);
        result.setCode(code);
        return result;
    }
    public static <T> Result<T> fail (String errMsg){
        Result<T> result=new Result();
        result.setCode(ResultStatusEnum.FILE.getCode());
        result.setErrMsg(errMsg);
        return result;
    }
    public static <T> Result<T> fail (String errMsg,T data){
        Result<T> result=new Result();
        result.setCode(ResultStatusEnum.FILE.getCode());
        result.setErrMsg(errMsg);
        result.setData(data);
        return result;
    }

}

1, 统一返回

①该类引用ResponseBodyAdvice接口

②加上类注解:@ControllerAdvice

java 复制代码
@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {
    @Autowired
    private ObjectMapper objectMapper;
    @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 Result){
            return body;
        }
        if (body instanceof String){
            return objectMapper.writeValueAsString(Result.ok(body));
        }
        return Result.ok(body);
    }
}

方法support方法相当于开关,当返回true的时候就会执行下面的方法进行封装.如果返回false.当前转换器会被跳过.如果代码返回的是字符串的时候,通过这个类包装后还是一个字符串,当原始返回值是 String 时, Spring 会优先使用 StringHttpMessageConverter(纯文本转换器),结果并不是一个JSON对象,即{code:200, data:"success", ...},就会报错. 所以我们需要手动转化一下,下面这个方法通过 ObjectMapper 将 Result 对象转为 JSON 字符串(如 "{\"code\":200,\"data\":\"success\"}")

objectMapper.writeValueAsString(Result.ok(body));

2, 统一异常拦截

①类注解@ResponseBody: 将Result对象转为JSON

②类注解@ControllerAdvice

③方法注解@ExceptionHandler

在这里可以拦截不同的异常,并返回不同的状态码,例如HandlerMethodValidationException和MethodArgumentNotValidException就是参数异常会报错的类,我们在这里统一进行处理,并且返回401,这样这个错误就直接反馈到了前端,也可以拦截自定义异常. 通常情况下,我会将未知的错误详细的信息以堆栈的方式打印出来,已知的报错只打印错误信息.

java 复制代码
@Slf4j
@ResponseBody      
@ControllerAdvice
public class ExceptionAdvice {
    @ExceptionHandler(Exception.class)
    public Result handle(Exception e ){
       log.error("发生异常,errMsg: ",e);
       return Result.fail(e.getMessage(),e);
    }
    @ExceptionHandler(BlogException.class)
    public Result handle(BlogException e ){
        log.error("发生异常,errMsg: {}",e.getErrMsg());
        return Result.fail(e.getErrMsg());
    }
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler({HandlerMethodValidationException.class, MethodArgumentNotValidException.class})
    public Result HandlerMethodValidationException(Exception e){
        log.error("参数不合法,e:{}",e.getMessage());
        return  Result.fail("参数不合法",e.getMessage());
    }
}

创建一个自定义异常

创建一个自定义异常,就可以处理一些"功能执行异常"的错误,例如用户名或密码错误,插入失败,token失效...

java 复制代码
@Data
public class BlogException extends RuntimeException{
    private int code;
    public String errMsg;

    public BlogException() {
    }

    public BlogException(int code, String errMsg) {
        this.code = code;
        this.errMsg = errMsg;
    }

    public BlogException(String errMsg) {
        this.errMsg = errMsg;
    }
}

3, 拦截器

当用户用访问url访问的时候,首先需要确认用户是否登录,这时就会通过验证是否有token,且token是否合法,有才来验证用户是否有权限访问网页,例如,用户直接访问博客列表的时候,由于用户之前没有登陆,没有token,就会被拦截.从而跳转到登录界面,让用户进行登录,拦截器部分在用户登录接口讲完后还会进行详细讲解

四,搭建三层框架

之后我们来创建基本的框架,即Controller层,Service层,Mapper层(之前已经建过)

并标上相对应的注解,并注入相关依赖

java 复制代码
@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {
    @Resource(name = "userInfoService")
    private UserService userService;
}
java 复制代码
public interface UserService {}

@Slf4j
@Service
public class UserInfoService implements UserService { 
    @Autowired
    private UserInfoMapper userInfoMapper;
    @Autowired
    private BlogInfoMapper blogInfoMapper;

}
   
java 复制代码
@RestController
@RequestMapping("/blog")
@Slf4j
public class BlogController {
    @Resource(name = "blogInfoService")
    private BlogService blogService;
}
java 复制代码
public interface BlogService {}

@Service
@Slf4j
public class BlogInfoService implements BlogService {
    @Autowired
    private BlogInfoMapper blogInfoMapper;
    @Autowired
    private UserInfoMapper userInfoMapper;
}

以上准备工作准备的差不多了,我们来正式写第一个接口

五,登录

登录这里我们将会用到jwt技术,对密码加盐加密,把token传到前端进行本地储存

/user/login POST

参数

{

"UserName": "xxx",

"password":"xxxxx"

}

响应

{

"code":200,

"errMsg": null,

"data":

{

"id" :xx,

"token":"xxxxxxx"

}

}

1,加盐加密

原因:由于有些用户的密码会存储在数据库中,但是用户密码这种涉及用户隐私的数据需要进行加密处理,加密的算法有很多,这里我们选用MD5,MD5算法可以将一个不论多长的字符串转化成都加密成一个定长字符串.而且理论上加密的数据是不能进行解密的,但是由于相同的字符串通道MD5加密后生成的字符串是一样的,所以网络上出现了MD5解密软件,但实际上它的原理是将你输入的MD5加密后的字符串,与它的数据库里的结果进行遍历,直到找到与之对应的,并找到对应加密之前的字符串. 这就导致如果数据库中只储存比较简单的加密后的密码,就会叫轻易的被破解掉.所以为了应对这一问题, 将会将用户的密码加盐,之后将整个字符串在进行MD5加密.

盐是一串随机生成的字符串,这样极大地增加了密码的复杂性,不会轻易的被破解掉,但由于盐是随机生成的,所以为了后续验证用户的密码是否正确,所以也需要将盐储存起来,由此数据库中的是密码组成为[MD5[密码+盐]]+盐. 用户登录的时候,验证用户密码输入的是否正确时,由于MD5无法解密的特性,我们无法得知用户真实的密码.但由于MD5的特性,相同的字符串加密之后的生成的字符串是一致的,所以将用户数据库中密码字符串中的盐提取出来.再将用户输入的密码再次按照之前加密加盐的顺序,再次进行加密,并将加密后的字符串和数据库中的字符串进行对比,如果字符串相等,则说明用户输入的正确,但是如果不一致,则说明用户输入错误.【其中密码和盐的顺序,加密后的字符串和盐的顺序,均可以随意调换,但需要注意的是,加密的时候顺序要和验证时加密的顺序要一致】

我们使用UUID.randomUUID().toString()生成一个随机字符串,由于原始生成的里面含有"-",所以我们将"-"用空字符串代替"". 然后进行加密加盐,返回生成的字符串,验证的时候,取出盐,将输入的密码加密加盐,并进行比较,返回一个boolean类型

java 复制代码
public static String encrypt(String password){
        String salt = UUID.randomUUID().toString().replace("-","");
        String security = (DigestUtils.md5DigestAsHex((salt + password).getBytes(StandardCharsets.UTF_8)))+salt;
        return security;
    }

    public static boolean verify(String SQLSecurity , String password){
        String salt = SQLSecurity.substring(32, 64);
        String security = (DigestUtils.md5DigestAsHex((salt + password).getBytes(StandardCharsets.UTF_8)))+salt;
        return SQLSecurity.equals(security);
    }

2,jwt令牌技术

令牌技术是为了验证用户进行了登录,用户登录之后就会又有这个令牌,别再访问其他url的时候,由于拥有令牌就可以随意的访问,相当于申请了一个通行证,反之如果没有令牌,用户访问URL的时候就会报401错误,并跳转到登录界面让用户进行登录.

JWT分为三个部分:

header:包括令牌的类型,以及使用的哈希算法

payload(负载):存放一些有效信息,可以进行自定义

signature:此部分防止JWT内容被篡改,确保安全性

首先引入依赖

XML 复制代码
    <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-api</artifactId>
			<version>0.11.5</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-impl</artifactId>
			<version>0.11.5</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
			<version>0.11.5</version>
			<scope>runtime</scope>
		</dependency>

首先生成一个Key,由于key有长度限制,所以直接采用以下算法,这里是指签名的算法采用HS256

Keys.secretKeyFor(SignatureAlgorithm.HS256);

之后通过Base64进行编码,生成一个字符串,这就是签名. 之后将签名解码并用以下方法,生成key

Keys.hmacShaKeyFor()

之后就可以生成token了.其中在payload放入一些自定义的数据,例如用户id,用户名,亦可以设置token有效时间

解析token的时候,通过如下方法创建一个解析器

Jwts.parserBuilder().setSigningKey(key).build();

java 复制代码
public class JwtUtil {
    private static final String encode="+CXy+rc7+2HBu7rvApWzzKwjONEZo2FEs6Uinpbo13I=";
    private static final  SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(encode));
    private static final long expiration= 24*60*60*1000 ;
    public static String getToken(Map<String,Object> map){
        String token = Jwts.builder()
                .setClaims(map)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                .signWith(key)
                .compact();
        return token;
    }
    public static Claims parseToken(String token){
        if (!StringUtils.hasLength(token)){
            return null;
        }
        JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();
        Claims body =null;
        try {
            body=build.parseClaimsJws(token).getBody();
        }catch ( SignatureException e){
            throw new BlogException(HttpStatus.UNAUTHORIZED.value(), "签名异常");
        }catch (ExpiredJwtException e){
            throw new BlogException(HttpStatus.UNAUTHORIZED.value(),"token超时");
        }
        catch (Exception e){
           throw new BlogException(HttpStatus.UNAUTHORIZED.value(),"token解析失败");
        }
        return body;

    }

    public  String getKey(){
        SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
        String encode = Encoders.BASE64.encode(secretKey.getEncoded());
        return encode;
    }

3,登录代码

之后正式进入登录的代码,首先根据接口文档,得知我们收到是是一个JSON字符串转化成的对象,所以我们创建一个与之属性对应的类,这个类是用来接收前端发来的数据. 后端收到前端发来的数据时,要对数据进行最基础的校验,例如是否为空,最大长度是否超过指定值,这时我们就会用到参数校验的注解,在此之前也需要加上参数校验的依赖. 由于返回也有多个数值,所以我们也将其分装成一个类,之后返回时,直接返回这个类就可以

XML 复制代码
        <dependency><!--参数校验-->
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-validation</artifactId>
		</dependency>
java 复制代码
@Data
public class LoginParam {
    @Length(max = 20)
    @NotBlank(message = "用户名不能为空")
    private String userName;
    @Length(max = 20,min = 4)
    @NotBlank(message = "密码不能为空")
    private String password;
}
java 复制代码
@Data
public class LoginResponse {
    private Integer id;
    private String token;
}

在相应的属性上加上校验注解后,如果发现前端传过来的参数不合格,就会直接返回400状态码,直接就将错误抛回前端了,就不会继续进入后端.执行Controller后,需要注意的是,当参数为一个对象,则需要加上@RequestBody,当这个对象里面使用的参数校验的注解,在这里就需要写上@Validated,使属性上的校验注解生效

java 复制代码
#Controller
@RequestMapping("/login")
    public LoginResponse login(@Validated @RequestBody LoginParam param){
        log.info("开始执行login...");
        return userService.login(param);
    }

进入Service代码

java 复制代码
#Service
 @Override
    public LoginResponse login(LoginParam param) {
        UserInfo userInfo = searchUserByName(param.getUserName());
        if (userInfo==null){
            throw new BlogException("该用户不存在");
        }
        if (!Security.verify(userInfo.getPassword(),param.getPassword())){
            throw new BlogException("用户名或密码错误");
        }
        Map<String,Object> map=new HashMap<>();
        map.put("userId",userInfo.getId());
        map.put("userName",userInfo.getUserName());
        String token = JwtUtil.getToken(map);
        LoginResponse loginResponse=new LoginResponse();
        loginResponse.setId(userInfo.getId());
        loginResponse.setToken(token);
        return loginResponse;
    }

 private UserInfo searchUserByName(String userName){
        return userInfoMapper.selectOne(new LambdaQueryWrapper<UserInfo>()
                .eq(UserInfo::getUserName, userName)
                .eq(UserInfo::getDeleteFlag, 0));
    }

4,拦截器实现

在登录接口写完之后,我们就可以完成之前没有写完的拦截器了,拦截器要进行注册和初始化

注册:

①引用HandlerInterceptor接口

②类注解@Component,让拦截器被扫描

③重写preHandle方法

由于我们会将userid和token放到用户本地储存,之后通过前端代码,让每次发送请求的时候,都带有userId和token的请求头,这里就是通过获得请求中的请求头的信息,获得userId和token,然后将token进行验证.如果token中的信息为空,或者token中的userId信息与前端传过来的信息不符,则返回状态码401,并返回false,表示被拦截. 返回true.表示通过

java 复制代码
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String token = request.getHeader("user_header_token");
        String userId = request.getHeader("user_header_id");
        Claims claims = JwtUtil.parseToken(token);
        if (claims==null||!claims.get("userId").toString().equals(userId)){
            response.setStatus(HttpStatus.UNAUTHORIZED.value());
            return false;
        }
        return true;
    }
}

初始化

①引用WebMvcConfigurer接口

②类注解@Configuration

③把注册的拦截器注入进来

④重写addInterceptors方法,并定义拦截的路径

java 复制代码
@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor).addPathPatterns("/blog/**","/user/**")
                .excludePathPatterns("/user/login","/user/register","/user/getCaptcha","/user/checkCaptcha");
    }
}

至此后端代码完成,我们来实现前端代码,基础html就不在这里过多赘述,这里只主要讲ajax里面的逻辑

html 复制代码
<!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/common.css">
    <link rel="stylesheet" href="css/login.css">

</head>

<body>
    <div class="nav">
        <img src="pic/logo.jpg" alt="">
        <span class="blog-title">我的博客系统</span>
        <div class="space"></div>
        <a class="nav-span" href="blog_list.html">主页</a>
        <a class="nav-span" href="blog_edit.html">写博客</a>
    </div>

    <div class="container-login">
        <div class="login-dialog">
            <h3>登陆</h3>
            <div class="row">
                <span>用户名</span>
                <input type="text" name="username" id="username">
            </div>
            <div class="row">
                <span>密码</span>
                <input type="password" name="password" id="password">
            </div>
            <div class="captcha">
                <input type="text" name="inputCaptcha" id="inputCaptcha">
                <img id="verificationCodeImg" src="/user/getCaptcha" style="cursor: pointer;" title="看不清?换一张" />
            </div>
            <div class="row">
                <button id="submit" onclick="login()">登录</button>
            </div>
            <div class="row">
                <a id="register" href="blog_register.html">注册</a>
            </div>

        </div>
    </div>
    <script src="js/jquery.min.js"></script>
    <script src="js/common.js"></script>
    <script>
       ...
    </script>
</body>

</html>

在省略号的地方我们开始写逻辑,由于我们传到后端的是一个JSON对象,所以这里需要以下代码

①contentType:"application/json"

②data:JSON.stringify({})

之后我们需要将传来的结果中的userId和token进行本地存储

localStorage.setItem("名称",数值);

javascript 复制代码
     $.ajax({
                type:"post",
                url:"user/login",
                contentType:"application/json",
                data:JSON.stringify ({
                   "userName" : $("#username").val(),
                    "password" : $("#password").val()
                }),
                success : function(result){
                    if(result.code==200&&result.data!=null){
                        // console.log("verify.code==200");
                        let data=result.data;
                        localStorage.setItem("user_id",data.id);
                        localStorage.setItem("user_token",data.token);
                        location.assign("blog_list.html?currentPage=1");
                    }else{
                        alert(result.errMsg);
                    }
                }

            });

前端拦截器相对应的配置,即将本地储存的两个变量以请求头的形式传给后端,由于每个请求的请求头里都要有这两个变量,所以每次发送请求的时候都要进行设置,所以将这段代码放到公共代码区里.

用这个方法即,每次发送请求前都会执行

$(document).ajaxSend(function (e, xhr, opt) {...});

得到本地存储的数据

localStorage.getItem("名称");

设置为请求头

xhr.setRequestHeader("请求头名称",数值);

javascript 复制代码
$(document).ajaxSend(function (e, xhr, opt) {
    console.log("进入ajaxSend");
    let token=localStorage.getItem("user_token");
    let userId=localStorage.getItem("user_id");
    xhr.setRequestHeader("user_header_token",token);
    xhr.setRequestHeader("user_header_id",userId);
});

六,验证码

/user/checkCaptcha POST

参数

{

"code": "xxx",

}

响应

{

"code":200,

"errMsg": null,

"data": true

}

这里我们直接调用Hutool里面已经实现的,进行调用.

首先引入依赖:

XML 复制代码
        <dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-captcha</artifactId>
			<version>5.8.37</version>
		</dependency>

HutoolController

java 复制代码
    @RequestMapping("/getCaptcha")
    public void getCaptcha(HttpServletResponse response, HttpSession session){
        userService.getCaptcha(response,session);
    }
    @RequestMapping("/checkCaptcha")
    public boolean checkCaptcha(@NotBlank String code, HttpSession session){
        return userService.checkCaptcha(code,session);
    }

Service里面涉及很多配置相关的变量,所以将其统一放到一个类里面方便管理,之后将生成的验证码放到session里面,方便后续验证是否正确.之后我们将生成的验证码图片,允许直接向 HTTP 响应体写入二进制数据,返回到前端.并设置相关返回类型. 验证的时候从session中获得数据和前端传来的进行对比,并验证是否超时

session.setAttribute("名称",数值);
防止缓存 response.setHeader("Pragma","No-cache");

java 复制代码
public class Properties {
    public static final int width =100;
    public static final int height =35;
    public static final int overTime = 60*1000;
}
java 复制代码
 @Override
    public void getCaptcha(HttpServletResponse response, HttpSession session) {
        LineCaptcha lineCaptcha = CaptchaUtil.createLineCaptcha(Properties.width, Properties.height);
        session.setAttribute("captcha_create_time",new Date());
        session.setAttribute("captcha_code",lineCaptcha.getCode());
        try {
            lineCaptcha.write(response.getOutputStream());
            response.setContentType("image/jpeg");
            response.setCharacterEncoding("UTF-8");
            //防止缓存
            response.setHeader("Pragma","No-cache");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        log.info("验证码为: {}",lineCaptcha.getCode());
    }
java 复制代码
 @Override
    public boolean checkCaptcha(String code, HttpSession session) {
        if(session==null){
            throw new BlogException("验证码不存在,请重新刷新");
        }
        String captcha_code = session.getAttribute("captcha_code").toString();
        Date captcha_create_time = (Date)session.getAttribute("captcha_create_time");
        if (captcha_create_time==null
                ||(System.currentTimeMillis()-captcha_create_time.getTime())>Properties.overTime){
            throw new BlogException("验证码超时,请重试!");
        }
        return captcha_code.equalsIgnoreCase(code);
    }

前端代码:

首先更改一下html中的验证码图片的来源,为src="/user/getCaptcha",有同一个目录下可直接这样写

html 复制代码
<div class="captcha">
    <input type="text" name="inputCaptcha" id="inputCaptcha">
    <img id="verificationCodeImg" src="/user/getCaptcha" style="cursor: pointer;" title="看不清?换一张" />
</div>
javascript 复制代码
    $.ajax({
                type:"post",
                url:"user/checkCaptcha",
                data:{
                    "code" : $("#inputCaptcha").val(),
                },
                success : function(result){
                    if(result.code==200&&result.data!=null&&result.data==true){
                        // console.log("验证码验证通过");
                        verify();//进行用户名和密码的校验
                    } else{
                        alert("验证码错误!");
                    }
                }

            });

七,注册

/user/register POST

参数

{

"userName": "xxxx",

"password": "xxxxx",

"githubUrl":"xxxxx"选填

}

响应

{

"code":200,

"errMsg": null,

"data": true

}

java 复制代码
@Data
public class RegisterParam {
    @Length(max = 20)
    @NotBlank(message = "用户名不能为空")
    private String userName;
    @Length(max = 20,min = 4)
    @NotBlank(message = "密码不能为空")
    private String password;
    @URL(message = "必须是有效的网址格式(如 http://example.com)")
    private String githubUrl;
}
java 复制代码
@RequestMapping("/register")
    public boolean register(@Validated @RequestBody RegisterParam param){
        log.info("开始执行register...");
        return userService.register(param);
    }
java 复制代码
 @Override
    public boolean register(RegisterParam param) {
        UserInfo userInfo1 = searchUserByName(param.getUserName());
        if (userInfo1!=null){
            throw new BlogException("用户名重复!");
        }
        UserInfo userInfo=new UserInfo();
        userInfo.setUserName(param.getUserName());
        userInfo.setPassword(Security.encrypt(param.getPassword()));
        if (param.getGithubUrl()!=null){
            userInfo.setGithubUrl(param.getGithubUrl());
        }
        int result = userInfoMapper.insert(userInfo);
        if (result<1){
            throw new BlogException("注册失败");
        }else {
            return true;
        }
    }
html 复制代码
<!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/common.css">
    <link rel="stylesheet" href="css/login.css">

</head>

<body>
    <div class="nav">
        <img src="pic/logo.jpg" alt="">
        <span class="blog-title">我的博客系统</span>
        <div class="space"></div>
        <a class="nav-span" href="blog_login.html">返回</a>
    </div>

    <div class="container-login">
        <div class="login-dialog" style="display: inline-block;  width: 500px; height: 428px; ">
            <h3>注册</h3>
            <div class="row" style=" height: 20px"></div>
            <div class="row" style=" width: 320px;">
                <span>用户名</span>
                <input type="text" name="username" id="username" placeholder="必填">
            </div>
            <div class="row" style=" width: 320px;">
                <span>github url</span>
                <input type="url" placeholder="选填 " pattern="https?://.+" name="githubUrl" id="githubUrl"  title="请输入http://或https://开头的网址">
            </div>
            <div class="row" style=" width: 320px;">
                <span>密码</span>
                <input type="password" name="password" id="password" placeholder="必填">
            </div>
            <div class="row" style=" width: 320px;">
                <span style="display: inline-block; width: 66px">确认密码</span>
                <input type="password" name="repeatPassword" id="repeatPassword" placeholder="必填">
            </div>
            <div class="row" style="display: inline-block;  width: 280px; height: 20px; display: flex; justify-content: flex-end;">
                <span style="display: inline-block; font-size: 12px; color: #999; text-align: right;">
                    密码长度最少4位,最长20位
                </span>
            </div>
            <div class="row">
                <button id="submit" onclick="registor()">提交</button>
            </div>

        </div>
    </div>
    <script src="js/jquery.min.js"></script>
    <script src="js/common.js"></script>

    <script>
        function verify() {
            if ($("#password").val() == $("#repeatPassword").val()) {
                return true;
            } else {
                return false;
            }
        }

        function registor() {
            if (!verify()) {
                alert("密码不一致")
                return;
            }
            $.ajax({
                type: "post",
                url: "user/register",
                contentType: "application/json",
                data: JSON.stringify({
                    "userName": $("#username").val(),
                    "password": $("#password").val(),
                    "githubUrl": $("#githubUrl").val()
                }),
                success: function (result) {
                    if (result.code == 200 && result.data != null && result.data == true) {
                        location.assign("blog_login.html");
                    } else {
                        alert(result.errMsg);
                    }
                }

            });

        }
    </script>
</body>

</html>

八,博客列表

/blog/getList?currentPage=? get

参数

{ }

响应

{

"code": 200,

"errMsg": null,

"data": {

"total": 9,

"records": [

{

"id": 1,

"title": "第一篇博客",

"content": "111我是博客正文我是博客正文我是博客正文111我是博客正文我是博客正文我是博客正文111我是博客正",

"userId": 1,

"author": "zhangsan",

"updateTime": "2025-04-23 13:55:08"

},

{

.....

},

....

}

由于想要对列表进行分页,所以需要给后端传过去当前的页面currentPage,想要分页,还需要知道每一页的博客数,以及查询博客的时候的偏离量select* BlogInfo limit 10,5),所以在后端也需要一个类来存放这些信息,其中records来存放博客的具体信息,因此也需要定义一个对象

java 复制代码
@Data
public class PageBlogListParam {
    private Integer currentPage=1;
    private Integer pageSize=5;
    private Integer offset;

    public Integer getOffset(){
        return (currentPage-1)*pageSize;
    }
}
java 复制代码
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageBlogListResponse <T>{
    private long total;
    private List<T> records;
    private PageBlogListParam request;
}
java 复制代码
@Data
public class BlogListResponse {
    private Integer id;
    private String title;
    private String content;
    private Integer userId;
    private String author;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date updateTime;

    public String getContent() {
        if (content.length()>=50){
            return content.substring(0,50);
        }else {
            return content;
        }

    }
}

对时间格式化

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")

Controller

因为这里的参数并不是从前端过来的全部传过来的,而是只穿来一个单一的参数,所以这里并不需要写备注@RequestBody

java 复制代码
@RequestMapping("/getList")
    public PageBlogListResponse<BlogListResponse> getBlogList(PageBlogListParam param){
        log.info("开始执行getListController...");
        return blogService.getBlogList(param);
    }

想要使用Mybatis-plus查询数据的总数

blogInfoMapper.selectCount(...);

last() 方法在 MyBatis-Plus 中用于直接拼接 SQL 片段 到生成的 SQL 语句末尾

.last("LIMIT " + param.getOffset() + "," + param.getPageSize()));

将查询的数据结果的对象全部转化成拥有相同元素的对象,可以使用for循环,或者使用foreach,这里使用另一种方法

①blogInfos.stream()List<BlogInfo> 转换为 Stream<BlogInfo>,以便进行流式处理

②.map(blogInfo -> { ... }) 对每个**BlogInfo**对象进行转换:

③collect(Collectors.toList())Stream<BlogListResponse> 重新收集为 List<BlogListResponse>,得到最终结果。

java 复制代码
 @Override
    public PageBlogListResponse<BlogListResponse> getBlogList(PageBlogListParam param) {
        log.info("开始执行getListService...");
        // 先查询总数
        Long total = BlogListCount();
        // 再查询分页数据
        List<BlogInfo> blogInfos = blogInfoMapper.selectList(new LambdaQueryWrapper<BlogInfo>()
                .eq(BlogInfo::getDeleteFlag, 0)
                .last("LIMIT " + param.getOffset() + "," + param.getPageSize()));
        //转化
        List<BlogListResponse> collect=conver(blogInfos);
        return new PageBlogListResponse<BlogListResponse>(total,collect,param);
    }

    private long BlogListCount(){
        return blogInfoMapper.selectCount(new LambdaQueryWrapper<BlogInfo>()
                .eq(BlogInfo::getDeleteFlag, 0));
    }

    private List<BlogListResponse> conver (List<BlogInfo> blogInfos ){
        return blogInfos.stream()
                .map(blogInfo -> {
                    BlogListResponse response = BeanConver.trans(blogInfo);
                    String userName = userInfoMapper.selectOne(new LambdaQueryWrapper<UserInfo>()
                            .eq(UserInfo::getId, blogInfo.getUserId())).getUserName();
                    response.setAuthor(userName);
                    return response;
                })
                .collect(Collectors.toList());
    }

前端

html 复制代码
<!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/common.css">
    <link rel="stylesheet" href="css/list.css">
    <link rel="stylesheet" href="css/bootstrap.min.css">

</head>

<body>
    <div class="nav">
        <img src="pic/logo.jpg" alt="">
        <span class="blog-title">我的博客系统</span>
        <div class="space"></div>
        <a class="nav-span" href="blog_list.html">主页</a>
        <a class="nav-span" href="blog_edit.html">写博客</a>
        <a class="nav-span" href="#" onclick="logout()">退出</a>
    </div>

    <div class="container">
        <div class="left">
            <div class="card">
                <img src="pic/doge.jpg" alt="">
                <span style="display: block;  color:gray ; text-align: center">个人资料</span>
                <h3></h3>
                <a class="GitHub" href="#">GitHub 地址</a>
                <div class="row" style="display: block; align-items: center;">
                    <a class="nav-span" href="blog_personal_list.html?currentPage=1">文章</a>
                    <span class="blogCount" style=" padding-left: 10px;"></span>
                </div>
            </div>
        </div>

        <div class="right">


        </div>
        <div class="demo" style="background-color: transparent; ">
            <ul id="pageContainer" class="pagination justify-content-center"></ul>
        </div>
    </div>

    <script src="js/jquery.min.js"></script>
    <script src="js/common.js"></script>
    <script src="js/jq-paginator.js"></script>
    <script>

        //显示用户信息
        var userUrl = "/user/getUserInfo?userId=" + localStorage.getItem("user_id");
        getUserInfo(userUrl);
        //显示博客列表
        getBlogList();
        function getBlogList() {
            $.ajax({
                type: "get",
                url: "/blog/getList" + location.search,
                success: function (result) {
                    if (result != null && result.code == 200 && result.data != null) {
                        let finalHtml = "";
                        let blogs = result.data.records;
                        for (var blog of blogs) {
                            finalHtml += '<div class="blog">';
                            finalHtml += '<div class="title">' + blog.title + '</div>';
                            finalHtml += '<div class="date">';
                            finalHtml += '<apen class="date-time">' + blog.updateTime + '</apen>';
                            finalHtml += '<apen class="author-name">' + blog.author + '</apen>';
                            finalHtml += '</div>';
                            finalHtml += '<div class="desc">' + blog.content + '</div>';
                            finalHtml += '<a class="detail" href="blog_detail.html?blogId=' + blog.id + '">查看全文&gt;&gt;</a>';
                            finalHtml += '</div>';
                        }
                        $(".right").html(finalHtml);

                        //翻页信息
                        $("#pageContainer").jqPaginator({
                            totalCounts: result.data.total, //总记录数
                            pageSize: 5,    //每页的个数
                            visiblePages: 5, //可视页数
                            currentPage: result.data.request.currentPage,  //当前页码
                            first: '<li class="page-item"><a class="page-link">first</a></li>',
                            prev: '<li class="page-item"><a class="page-link" href="javascript:void(0);">up<\/a><\/li>',
                            next: '<li class="page-item"><a class="page-link" href="javascript:void(0);">down<\/a><\/li>',
                            last: '<li class="page-item"><a class="page-link" href="javascript:void(0);">last<\/a><\/li>',
                            page: '<li class="page-item"><a class="page-link" href="javascript:void(0);">{{page}}<\/a><\/li>',

                            //页面初始化和页码点击时都会执行
                            onPageChange: function (page, type) {
                                console.log("第" + page + "页, 类型:" + type);
                                if (type == "change") {
                                    location.href = "blog_list.html?currentPage=" + page;
                                }
                            }
                        });
                    }
                }
            });
        }

    </script>
</body>

</html>

需要注意的是,下面两者要对应上

九,获得个人博客列表

获得个人博客列表的程序,只是多了一个作者本人的查询条件,这里就可以直接从请求头中获取信息

java 复制代码
@Override
    public PageBlogListResponse<BlogListResponse> getPersonalList(PageBlogListParam param, HttpServletRequest request) {
        Long total = BlogListCount();
        List<BlogInfo> blogInfos = blogInfoMapper.selectList(new LambdaQueryWrapper<BlogInfo>()
                .eq(BlogInfo::getDeleteFlag, 0)
                .eq(BlogInfo::getUserId,request.getHeader("user_header_id"))
                .last("LIMIT " + param.getOffset() + "," + param.getPageSize()));
        List<BlogListResponse> collect=conver(blogInfos);
        return new PageBlogListResponse<BlogListResponse>(total,collect,param);
    }

个人列表的前端代码与博客列表页是相似的,这里就不多进行赘述了

十,获得博客细节

/blog/getBlogDetail?blogId=? get

参数

{ }

响应

{

"code": 200,

"errMsg": null,

"data": {

"id": 1,

"title": "第一篇博客",

"content": "111我是博客正文我是博客正文我是博客正文111我是博客正文我是博客正文我是博客正文111我是博客正文我是博客正文我是博客正文111我是博客正文我是博客正文我是博客正文111我是博客正文我是博客正文我是博客正文111我是博客正文我是博客正文我是博客正文111我是博客正",

"userId": 1,

"updateTime": "2025-04-23 13:55:08"

}

}

由于返回的是完整的博客,所以这个返回类就要和之前的列表类区分开来

java 复制代码
@Data
public class BlogDetailResponse {
    private Integer id;
    private String title;
    private String content;
    private Integer userId;
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date updateTime;
}
java 复制代码
   @Override
    public BlogDetailResponse getBlogDetail(Integer blogId) {
        log.info("开始执行getBlogDetailService...");
        BlogInfo blogInfo = blogInfoMapper.selectOne(new LambdaQueryWrapper<BlogInfo>()
                .eq(BlogInfo::getId, blogId)
                .eq(BlogInfo::getDeleteFlag, 0));
        if (blogInfo==null){
            throw new BlogException("查询不到该博客,Id为:[ "+blogId+" ]的博客不存在");
        }
        return BeanConver.trans1(blogInfo);
    }

public static BlogDetailResponse trans1 (BlogInfo blogInfo){
        BlogDetailResponse blogDetailResponse=new BlogDetailResponse();
        BeanUtils.copyProperties(blogInfo,blogDetailResponse);
        return blogDetailResponse;
    }

前端

这里使用的是markdown.由于这里从数据库里面传来的数据需要通过markdown来呈现出来,注意这里的detail是指id,所以要在html的语句上加id属性,在此之前也要引入依赖

<link rel="stylesheet" href="blog-editormd/css/editormd.css" />

<script src="js/jquery.min.js"></script>

<script src="blog-editormd/lib/marked.min.js"></script>

<script src="blog-editormd/lib/prettify.min.js"></script>

<script src="blog-editormd/editormd.js"></script>

<script src="js/common.js"></script>
editormd.markdownToHTML("detail", {

markdown: blog.content,

});

html 复制代码
<!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/common.css">
    <link rel="stylesheet" href="css/detail.css">

</head>

<body>
    <div class="nav">
        <img src="pic/logo.jpg" alt="">
        <span class="blog-title">我的博客系统</span>
        <div class="space"></div>
        <a class="nav-span" href="blog_list.html">主页</a>
        <a class="nav-span" href="blog_edit.html">写博客</a>
        <a class="nav-span" href="#" onclick="logout()">退出</a>
    </div>

    <div class="container">
        <div class="left">
            <div class="card">
                <img src="pic/doge.jpg" alt="">
                <span style="display: block;  color:gray ; text-align: center">作者信息</span>
                <h3></h3>
                <a class="GitHub" href="#">GitHub 地址</a>
                <div class="row" style="display: block; align-items: center;">
                    <span>文章</span>
                    <span class="blogCount" style=" padding-left: 10px;"></span>
                </div>
            </div>
        </div>
        <div class="right">
            <div class="content">
                <div class="title"></div>
                <div class="date"></div>
                <div class="detail" id="detail" style="background-color: transparent;"></div>

                <!-- <div class="operating">
                    <button onclick="window.location.href='blog_update.html'">编辑</button>
                    <button onclick="deleteBlog()">删除</button>
                </div> -->
            </div>

        </div>
    </div>

    <!-- 引入 editor.md 的依赖 -->
    <link rel="stylesheet" href="blog-editormd/css/editormd.css" />
    <script src="js/jquery.min.js"></script>
    <script src="blog-editormd/lib/marked.min.js"></script>
    <script src="blog-editormd/lib/prettify.min.js"></script>
    <script src="blog-editormd/editormd.js"></script>
    <script src="js/common.js"></script>
    <script>
        //三步:
        //1,引入依赖
        //2,更改ajax,其中里面的detail是指id
        //3,更改html加上 id=detail,背景颜色与父级颜色一致

        getBlogDetail();
        function getBlogDetail() {
            $.ajax({
                type: "get",
                url: "/blog/getBlogDetail" + location.search,
                success: function (result) {
                    if (result != null && result.code == 200 && result.data != null) {
                        let blog = result.data;
                        $(".right .content .title").text(blog.title);
                        $(".right .content .date").text(blog.updateTime);
                        editormd.markdownToHTML("detail", {
                            markdown: blog.content,
                        });
                        if (localStorage.getItem("user_id") == blog.userId) {
                            let finalHtml = "";
                            finalHtml += '<div class="operating">';
                            finalHtml += '<button onclick="window.location.href=\'blog_update.html' + location.search + '\'">编辑</button>'
                            finalHtml += '<button onclick="deleteBlog()">删除</button>';
                            finalHtml += '</div>';
                            $(".content").append(finalHtml);
                        }
                    }
                }
            });
        }
    </script>
</body>

</html>

十一,添加图书

//blog/add post

参数

{

"userId": "xxx"

"title": "xxxx",

"content": "xxx"

}

响应

{

"code": 200,

"errMsg": null,

"data": true

}

首先,前端传到后端是一个JSON字符串,所以要设置一个与之对应的类来接收

java 复制代码
@Data
public class AddBlogParam {
    @NotNull
    private Integer userId;
    @NotBlank(message = "标题不能为空")
    @Length(max = 25)
    private String title;
    @NotBlank(message = "内容不能为空")
    private String content;
}
java 复制代码
   @Override
    public boolean add(AddBlogParam param) {
        BlogInfo blogInfo=new BlogInfo();
        blogInfo.setTitle(param.getTitle());
        blogInfo.setUserId(param.getUserId());
        blogInfo.setContent(param.getContent());
        int result= blogInfoMapper.insert(blogInfo);
        if (result>0){
            return true;
        }else {
            throw new BlogException("插入失败,请联系管理员");
        }
    }

前端

由于要在前端显示出来markdown,所以html要加入markdown 插件 html代码,前端获取博客userId的时候,直接从本地存储获得

html 复制代码
CTYPE 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/common.css">
    <link rel="stylesheet" href="css/edit.css">
    <link rel="stylesheet" href="blog-editormd/css/editormd.css" />

</head>

<body>
    <div class="nav">
        <img src="pic/logo.jpg" alt="">
        <span class="blog-title">我的博客系统</span>
        <div class="space"></div>
        <a class="nav-span" href="blog_list.html">主页</a>
        <a class="nav-span" href="blog_edit.html">写博客</a>
        <a class="nav-span" href="#" onclick="logout()">退出</a>
    </div>
    <div class="content-edit">
        <div class="push">
            <input type="text" name="" id="title">
            <input type="button" value="发布文章" id="submit" onclick="submit()">
        </div>
        <!-- markdown 插件 html代码 -->
        <div id="editor">
            <textarea style="display:none;" id="content" name="content">##在这里写下一篇博客</textarea>
        </div>
    </div>

    <script src="js/jquery.min.js"></script>
    <script src="blog-editormd/editormd.min.js"></script>
    <script src="js/common.js"></script>
    <script type="text/javascript">

        $(function () {
            var editor = editormd("editor", {
                width: "100%",
                height: "550px",
                path: "blog-editormd/lib/"
            });
        });

        function submit() {
            $.ajax({
                type: "post",
                url: "/blog/add",
                contentType: "application/json",
                data: JSON.stringify({
                    "userId": localStorage.getItem("user_id"),
                    "title": $("#title").val(),
                    "content": $("#content").val()
                }),
                success: function (result) {
                    if (result != null && result.code == 200 && result.data != null && result.data == true) {
                        alert("发布成功");
                        location.assign("blog_personal_list.html?currentPage=1");
                    } else {
                        alert(result.errMsg);
                    }
                }

            });
        }
    </script>
</body>

</html>

十二,更新博客

1,显示博客内容

/blog/getBlogDetail?blogId=# get

参数

{ }

响应

{

"id": 1,

"title": "第一篇博客",

"content": "111我是博客正文我是博客正文我是博客正文111我是博客正文我是博客正文我是博客正文111我是博客正文我是博客正文我是博客正文111我是博客正文我是博客正文我是博客正文111我是博客正文我是博客正文我是博客正文111我是博客正文我是博客正文我是博客正文111我是博客正",

"userId": 1,

"updateTime": "2025-04-23 13:55:08"

}

后端代码之前已经写过了,前端直接调用就可以了,前端代码在后面一起呈现

2,修改博客

/blog/update post

参数

{

"id": "xxx"

"title": "xxxx",

"content": "xxx"

}

响应

{

"code": 200,

"errMsg": null,

"data": true

}

java 复制代码
@Data
public class UpdateBlogParam {
    @NotNull
    //blogId
    private Integer id;
    @NotBlank(message = "标题不能为空")
    @Length(max = 25)
    private String title;
    @NotBlank(message = "内容不能为空")
    private String content;
}
java 复制代码
@Override
    public boolean update(UpdateBlogParam param) {
        if (!IsLegal(param.getId())){
            throw new BlogException("更改失败,无此博客");
        }
        int result= blogInfoMapper.update(new LambdaUpdateWrapper<BlogInfo>()
                .eq(BlogInfo::getId,param.getId())
                .set(BlogInfo::getContent,param.getContent())
                .set(BlogInfo::getTitle,param.getTitle()));
        if (result>0){
            return true;
        }else {
            throw new BlogException("更新错误失败,请联系管理员");
        }
    }

 private boolean IsLegal(Integer blogId){
        BlogInfo blogInfo = blogInfoMapper.selectOne(new LambdaQueryWrapper<BlogInfo>()
                .eq(BlogInfo::getId, blogId)
                .eq(BlogInfo::getDeleteFlag, 0));
        if (blogInfo==null){
            return false;
        }else {
            return true;
        }
    }

前端

html 复制代码
<!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/common.css">
    <link rel="stylesheet" href="css/edit.css">
    <link rel="stylesheet" href="blog-editormd/css/editormd.css" />

</head>

<body>
    <div class="nav">
        <img src="pic/logo.jpg" alt="">
        <span class="blog-title">我的博客系统</span>
        <div class="space"></div>
        <a class="nav-span" href="blog_list.html">主页</a>
        <a class="nav-span" href="blog_edit.html">写博客</a>
        <a class="nav-span" href="#" onclick="logout()">退出</a>
    </div>
    <div class="content-edit">
        <div class="push">
            <input type="hidden" id="blogId">
            <input type="text"  id="title">
            <input type="button" value="更新文章" id="submit" onclick="submit()">
        </div>
        <!-- markdown 插件 html代码 -->
        <div id="editor">
            <textarea style="display:none;" id="content">##在这里写下一篇博客</textarea>
        </div>
    </div>

    <script src="js/jquery.min.js"></script>
    <script src="blog-editormd/editormd.min.js"></script>
    <script src="js/common.js"></script>
    <script type="text/javascript">

        getBlogInfo();

        function submit() {
            $.ajax({
                type: "post",
                url: "/blog/update",
                contentType: "application/json",
                data: JSON.stringify({
                    "id": $("#blogId").val(),
                    "title": $("#title").val(),
                    "content": $("#content").val()
                }),
                success: function (result) {
                    if (result != null && result.code == 200 && result.data != null && result.data == true) {
                        alert("更新成功");
                        location.assign("blog_detail.html" + location.search + "");
                    } else {
                        alert(result.errMsg);
                    }
                }

            });
        }
        function getBlogInfo() {
            $.ajax({
                type: "get",
                url: "/blog/getBlogDetail" + location.search,
                success: function (result) {
                    if (result != null && result.code == 200 && result.data != null) {
                        let blog = result.data;
                        $("#blogId").val(blog.id);
                        $("#title").val(blog.title);
                        $("#content").val(blog.content);
                        editormd("editor", {
                            width: "100%",
                            height: "550px",
                            path: "blog-editormd/lib/",
                            onload: function () {
                                this.watch();
                                this.setMarkdown(blog.content);
                            }
                        });
                    }
                }
            });
        }

    </script>
</body>

</html>

十三,删除博客

/blog/delete?blogId=# get

参数

{ }

响应

{

"code": 200,

"errMsg": null,

"data": true

}

java 复制代码
 @Override
    public boolean delete(Integer blogId) {
        if (!IsLegal(blogId)){
            throw new BlogException("删除失败,无此博客");
        }
        int result= blogInfoMapper.update(new LambdaUpdateWrapper<BlogInfo>()
                .eq(BlogInfo::getId,blogId)
                .set(BlogInfo::getDeleteFlag,1));
        if (result>0){
            return true;
        }else {
            throw new BlogException("删除失败,请联系管理员");
        }
    }
javascript 复制代码
 function deleteBlog() {
            if (!confirm("确认删除?")) {
                return;
            }
            $.ajax({
                type: "get",
                url: "/blog/delete" + location.search,
                success: function (result) {
                    if (result != null && result.code == 200 && result.data != null && result.data == true) {
                        alert("删除成功");
                        location.assign("blog_list.html?currentPage=1")
                    } else {
                        alert(result.errMsg);
                    }
                }

            });

十四,获得用户信息

/user/getUserInfo?userId=# get

参数

{ }

响应

{

"code": 200,

"errMsg": null,

"data": {

"id": 1,

"userName": "zhangsan",

"githubUrl": "https://gitee.com/bubble-fish666/class-java45",

"blogCount": 4

}

}

java 复制代码
 @Override
    public UserInfoResponse getUserInfo(Integer userId) {
        UserInfo userInfo = searchUserById(userId);
        UserInfoResponse userInfoResponse = BeanConver.trans(userInfo);
        userInfoResponse.setBlogCount(blogCount(userId));
        return userInfoResponse;
    }

   private long blogCount(Integer userId){
        return blogInfoMapper.selectCount(new LambdaQueryWrapper<BlogInfo>()
                .eq(BlogInfo::getUserId,userId)
                .eq(BlogInfo::getDeleteFlag,0));
    }


    private UserInfo searchUserById(Integer userId){
        return userInfoMapper.selectOne(new LambdaQueryWrapper<UserInfo>()
                .eq(UserInfo::getId, userId)
                .eq(UserInfo::getDeleteFlag, 0));
    }

十五,获得作者信息

/user/getAuthorInfo?blogId=# get

参数

{ }

响应

{

"code": 200,

"errMsg": null,

"data": {

"id": 1,

"userName": "zhangsan",

"githubUrl": "https://gitee.com/bubble-fish666/class-java45",

"blogCount": 4

}

}

java 复制代码
    @Override
    public UserInfoResponse getAuthorInfo(Integer blogId) {
        BlogInfo blogInfo = blogInfoMapper.selectOne(new LambdaQueryWrapper<BlogInfo>()
                .eq(BlogInfo::getId, blogId)
                .eq(BlogInfo::getDeleteFlag, 0));
        UserInfo userInfo = searchUserById(blogInfo.getUserId());
        UserInfoResponse userInfoResponse = BeanConver.trans(userInfo);
        userInfoResponse.setBlogCount(blogCount(userInfo.getId()));
        return userInfoResponse;
    }

前端:这两个使用的代码是一样的,只是URL不同,所以可以把getUserInfo写到公共代码区

javascript 复制代码
//显示用户信息
        var userUrl = "/user/getUserInfo?userId=" + localStorage.getItem("user_id");
        getUserInfo(userUrl);

//显示博客作者信息
        var userUrl = "/user/getAuthorInfo" + location.search;
        getUserInfo(userUrl);



function getUserInfo(url){
    $.ajax({
        type:"get",
        url: url,
        success : function(result){
            if(result!=null&&result.code==200&result.data!=null){
                let data=result.data;
                $(".container .left .card h3").text(data.userName);
                $(".container .left .card .GitHub").attr("href",data.githubUrl);
                $(".container .left .card .row .blogCount").text(data.blogCount);
            }else{
                alert(result.errMsg);
            }
        }
    });
}

十六,退出

写在公共代码区,只需要把本地存储的token和userId删除即可

javascript 复制代码
function logout(){
    if(!confirm("确认退出?")){
        return;
    }
    localStorage.removeItem("user_token");
    localStorage.removeItem("user_id");
    location.assign("blog_login.html");
}

最后补充一些前端的代码

统一异常处理

$(document).ajaxError(function(event,xhr,options,exc){

//写各种错误的处理方法

});

javascript 复制代码
$(document).ajaxError(function(event,xhr,options,exc){
    if(xhr.status==401){
        alert("用户未登录,请先登录");
        location.href="blog_login.html";
    }
    if(xhr.status==400){
        alert("参数不合法");
    }
});

最后,很感谢你能看到这里,这就是博客系统的全部内容,希望这些内容可以对你有所帮助

相关推荐
*.✧屠苏隐遥(ノ◕ヮ◕)ノ*.✧20 分钟前
MyBatis快速入门——实操
java·spring boot·spring·intellij-idea·mybatis·intellij idea
Wyc7240927 分钟前
JDBC:java与数据库连接,Maven,MyBatis
java·开发语言·数据库
烧瓶里的西瓜皮1 小时前
Go语言从零构建SQL数据库(9)-数据库优化器的双剑客
数据库·sql·golang
地理探险家2 小时前
各类有关NBA数据统计数据集大合集
数据库·数据集·数据·nba·赛季
bing_1582 小时前
Spring MVC 中Model, ModelMap, ModelAndView 之间有什么关系和区别?
java·spring·mvc
篱笆院的狗2 小时前
MySQL 中如何进行 SQL 调优?
java·sql·mysql
SelectDB技术团队3 小时前
顺丰科技:从 Presto 到 Doris 湖仓构架升级,提速 3 倍,降本 48%
大数据·数据库·数据仓库·信息可视化·数据分析·doris·实时分析
wangbaowo3 小时前
MySQL数据库下篇
数据库·经验分享·笔记·学习·mysql·安全
伤不起bb3 小时前
MySQl 数据库操作
数据库·mysql