当前位置: 首页 > web >正文

Spring-博客系统项目

一,实现效果

登录:

注册:

博客列表

个人博客中心

博客详情:

更新博客

编写博客

二,数据库的建立和连接

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

-- 建表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依赖

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

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

#数据库连接配置datasource:url: jdbc:mysql://127.0.0.1:3306/java_blog?characterEncoding=utf8&useSSL=falseusername: rootpassword: "****"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-loglogback:   #日志分割rollingpolicy:file-name-pattern: ${LOG_FILE}.%d{yyyy-MM-dd}.%imax-file-size: 1MB

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

@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;}
@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注解

@Mapper
public interface BlogInfoMapper extends BaseMapper<BlogInfo> {
}
@Mapper
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}

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

        <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通常放正常返回的数据内容,状态码我们放到一个枚举类里面

@Getter
@AllArgsConstructor
public enum ResultStatusEnum {SUCCESS(200),FILE(-1);private  int code;
}
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


@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice {@Autowiredprivate ObjectMapper objectMapper;@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@SneakyThrows@Overridepublic 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,这样这个错误就直接反馈到了前端,也可以拦截自定义异常. 通常情况下,我会将未知的错误详细的信息以堆栈的方式打印出来,已知的报错只打印错误信息.

@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失效...

@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层(之前已经建过)

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

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {@Resource(name = "userInfoService")private UserService userService;
}
public interface UserService {}@Slf4j
@Service
public class UserInfoService implements UserService { @Autowiredprivate UserInfoMapper userInfoMapper;@Autowiredprivate BlogInfoMapper blogInfoMapper;}
@RestController
@RequestMapping("/blog")
@Slf4j
public class BlogController {@Resource(name = "blogInfoService")private BlogService blogService;
}
public interface BlogService {}@Service
@Slf4j
public class BlogInfoService implements BlogService {@Autowiredprivate BlogInfoMapper blogInfoMapper;@Autowiredprivate 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类型

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内容被篡改,确保安全性

首先引入依赖

    <!-- 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();

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字符串转化成的对象,所以我们创建一个与之属性对应的类,这个类是用来接收前端发来的数据. 后端收到前端发来的数据时,要对数据进行最基础的校验,例如是否为空,最大长度是否超过指定值,这时我们就会用到参数校验的注解,在此之前也需要加上参数校验的依赖. 由于返回也有多个数值,所以我们也将其分装成一个类,之后返回时,直接返回这个类就可以

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

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

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

进入Service代码

#Service@Overridepublic 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.表示通过

@Component
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic 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方法,并定义拦截的路径

@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(loginInterceptor).addPathPatterns("/blog/**","/user/**").excludePathPatterns("/user/login","/user/register","/user/getCaptcha","/user/checkCaptcha");}
}

至此后端代码完成,我们来实现前端代码,基础html就不在这里过多赘述,这里只主要讲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/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("名称",数值);

     $.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("请求头名称",数值);

$(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里面已经实现的,进行调用.

首先引入依赖:

        <dependency><groupId>cn.hutool</groupId><artifactId>hutool-captcha</artifactId><version>5.8.37</version></dependency>

HutoolController

    @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");
public class Properties {public static final int width =100;public static final int height =35;public static final int overTime = 60*1000;
}
 @Overridepublic 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());}
 @Overridepublic 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",有同一个目录下可直接这样写

<div class="captcha"><input type="text" name="inputCaptcha" id="inputCaptcha"><img id="verificationCodeImg" src="/user/getCaptcha" style="cursor: pointer;" title="看不清?换一张" />
</div>
    $.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

}

@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;
}

@RequestMapping("/register")public boolean register(@Validated @RequestBody RegisterParam param){log.info("开始执行register...");return userService.register(param);}
 @Overridepublic 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;}}
<!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来存放博客的具体信息,因此也需要定义一个对象

@Data
public class PageBlogListParam {private Integer currentPage=1;private Integer pageSize=5;private Integer offset;public Integer getOffset(){return (currentPage-1)*pageSize;}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageBlogListResponse <T>{private long total;private List<T> records;private PageBlogListParam request;
}
@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

@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>,得到最终结果。

 @Overridepublic 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());}

前端

<!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>

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

九,获得个人博客列表

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

@Overridepublic 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"

    }

}

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

@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;
}
   @Overridepublic 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,

                        });

<!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字符串,所以要设置一个与之对应的类来接收

@Data
public class AddBlogParam {@NotNullprivate Integer userId;@NotBlank(message = "标题不能为空")@Length(max = 25)private String title;@NotBlank(message = "内容不能为空")private String content;
}
   @Overridepublic 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的时候,直接从本地存储获得

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

}

@Data
public class UpdateBlogParam {@NotNull//blogIdprivate Integer id;@NotBlank(message = "标题不能为空")@Length(max = 25)private String title;@NotBlank(message = "内容不能为空")private String content;
}
@Overridepublic 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;}}

前端

<!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

}

 @Overridepublic 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("删除失败,请联系管理员");}}
 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

    }

}

 @Overridepublic 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

    }

}

@Overridepublic 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写到公共代码区

//显示用户信息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删除即可

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){

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

});

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

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

http://www.xdnf.cn/news/4553.html

相关文章:

  • 动态规划之回文串问题
  • 第7章-3 维护索引和表
  • 添加地形与自定义地形
  • HTML基础2-空元素,元素属性与页面的结构
  • livedata使用,完整的livedata的Demo
  • Spring 中org.springframework.core.Ordered接口的实战教学
  • 在 ESP-IDF 中使用 .a 静态库调用
  • 解析表观遗传学的工具——ChIP-seq(一)
  • 数据库即服务(DBaaS)领域的最新创新
  • 每日一道leetcode
  • SCADA|KingSCADA运行报错:加载实时库服务失败
  • git 入门使用教程
  • 全国通用Y1大型游乐设施修理作业证精选题
  • PTS-G5K13M RF Generator 5kW / 13MHz 射频电源User s Manual
  • Spring Boot 如何自动配置事务管理器?
  • 数据结构之线性表
  • 阿里云codeup以及本地gitclone+http
  • Mybatis标签使用 -association 绑定对象,collection 绑定集合
  • ROS第十三梯:RViz+Marker——自定义几何形状可视化
  • 深度学习模型的部署实践与Web框架选择
  • 淘宝按图搜索商品(拍立淘)Java 爬虫实战指南
  • 拉削丝锥,螺纹类加工的选择之一
  • 1.3 Expression.Lambda表达式树的介绍
  • LWIP的超时事件笔记
  • 【python】使用Python和BERT进行文本摘要:从数据预处理到模型训练与生成
  • vllm命令行启动方式并发性能实测
  • 联想Horizon 2系列电脑 参数
  • SpringBoot学生宿舍管理系统开发实现
  • 浏览器跨标签通信的实现原理
  • feign负载均衡