一,实现效果
登录:
注册:
博客列表
个人博客中心
博客详情:

更新博客
编写博客
二,数据库的建立和连接
首先,需要建库,需要两个实体,一个是用户,一个是博客,需要如下属性,需要注意的是需要将密码的变长字符创设置的长一些,因为之后会对用户的密码进行加密,该博客中密码要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 + '">查看全文>></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("参数不合法");
}
});
最后,很感谢你能看到这里,这就是博客系统的全部内容,希望这些内容可以对你有所帮助