【】(综合练习)博客系统

在之前的学些中,我们掌握了Spring框架和MyBatis的基本使用,接下来 我们就要结合之前我们所学的知识,做出一个项目出来

  

  1.前期准备

                当我们接触到一个项目时,我们需要对其作出准备,那么正规的准备是怎么样的呢

         1.了解需求

                 我们在拿到一个项目的时候,需要确认需求的合理性,看需求有没有什么问题,有问题要及时反映

           2.方案设计

                       在拿到一个项目时,我们需要做以下的设计,此外还有更多需要设计

                       1)接口设计  2)数据库设计  3)架构图   4)流程图等等  5)测试  6)上线流程

            3.开发

                 当方案设计设计完之后,我们就需要完成开发了

            4.测试  

                     当完成开发之后,我们需要对项目进行测试,查看运行情况

             5.联调

                      联动其他部门进行调试

              6.提交测试(QA)

                        当调试完毕后,我们就需要将项目提交给测试人员进行测试

              7.上线

2.开发

                1.项目准备

               这次我们主要做的工作是后端的开发和实现,在此之前我们需要准备一些东西

              1.准备数据库数据

                

                我们想需要准备一个数据库

create database if not exists java_blog_spring charset utf8mb4;
-- ⽤⼾表
DROP TABLE IF EXISTS java_blog_spring.user;
CREATE TABLE java_blog_spring.user(
`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(),
PRIMARY KEY ( id ),
UNIQUE INDEX user_name_UNIQUE ( user_name ASC )) ENGINE = INNODB DEFAULT CHARACTER
SET = utf8mb4 COMMENT = '⽤⼾表';





-- 博客表
drop table if exists java_blog_spring.blog;
CREATE TABLE java_blog_spring.blog (
`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(),
PRIMARY KEY (id))
ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '博客表';



建好数据库后,我们准备一些数据

        2.创建SpringBoot项目,添加SpringMVC和MyBatis对应依赖

                

        3.将网页的的静态界面拷贝到我们的项目中去,详细可以看我们码云

https://gitee.com/qiu-shangda/java-code/tree/master/Blog-Systemhttps://gitee.com/qiu-shangda/java-code/tree/master/Blog-Systemhttps://gitee.com/qiu-shangda/java-code/tree/master/Blog-Systemhttps://gitee.com/qiu-shangda/java-code/tree/master/Blog-System

网页如图所示

      

                


           
              2.项目公共模块

                        项⽬分为控制层(Controller),服务层(Service),持久层(Mapper).各层之间                                  的调⽤关系如下:

                        

                        我们可以先完成公共模块的编写

                             1.先写出实体类                          

@Data
public class Blog {
private Integer id;
private String title;
private String content;
private Integer userId;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
}


@Data
public class User {
private Integer id;
private String userName;
private String password;
private String githubUrl;
private Byte deleteFlag;
private Date createTime;
private Date updateTime;
}

                2.编写mapper

                    在编写sql语句之前,我们要想一想整个项目的逻辑

                      1.用户登录,校验用户名和密码是否正确,根据用户名,查询用户信息,比对密码                             是否正确      

                         sql语句:根据用户名查询用户信息

                       2.博客页表页

                            sql:根据用户id,查询用户信息

                             sql:获取博客列表

                        3.博客详情页                

                               sql:根据博客id,获取博客详情

                                sql:根据博客id,编辑博客

                                 sql:根据博客id,删除博客

                        4.博客添加和修改

                                sql:根据输入内容,添加博客

                        

                     

  3.定义一个结果类,在里面放返回的成功的信息还是失败的信息                     

package com.example.blogsystem.Common;

public class Constants {
    public  static final Integer RESULT_SUCCESS=200;
    public  static  final  Integer RESULT_FAIL=-1;
}





package com.example.blogsystem.Model;

import com.example.blogsystem.Common.Constants;
import lombok.Data;

@Data
public class Result<T> {
    private int code;//200表示成功,-1表示失败
    private  String errorMsg;
    private T data;
    public  static <T> Result<T> success(T data){
        Result result=new Result();
        result.setCode(Constants.RESULT_SUCCESS);
        result.setData(data);
        return result;
    }

    public   static <T> Result<T> fail(String errorMsg,T data){
        Result result=new Result();
        result.setCode(Constants.RESULT_FAIL);
        result.setData(errorMsg);
        result.setData(data);
        return result;

    }
}

          4.统一功能管理

                  我们顺带的编写出统一功能管理,进行统一的返回结果

                

package com.example.blogsystem.config;

import com.example.blogsystem.Model.Result;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/*
统一返回结果
 */
public class ResponseAdvice implements ResponseBodyAdvice {

    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true;
    }

    @SneakyThrows
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if(body instanceof Result){
            return body;
        }
        if (body instanceof String){

            ObjectMapper objectMapper=new ObjectMapper();
            return objectMapper.writeValueAsString(Result.success(body));
        }
        return Result.success(body);
    }
}

        5.统一异常处理

            在程序运行的时候,我们总会遇到各种各样的异常和错误,所以这里我们来添加统一异常管理,来对异常进行统一处理 

        

package com.example.blogsystem.config;

import com.example.blogsystem.Common.Constants;
import com.example.blogsystem.Model.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ResponseBody
@ControllerAdvice
public class ErrorAdvice {
    @ExceptionHandler
    public Result errorHandler(Exception e){
                Result result=new Result<>();
                result.setErrorMsg("内部发生错误,请连接管理员");
                result.setCode(Constants.RESULT_FAIL);
                return  result;
                        
    }
}

 3.业务开发

        当我们完成公共模块的编写之后,我们就开始了业务模块的开发

3.1 持久层

根据需求,先⼤致计算有哪些DB相关操作,完成持久层初步代码,后续再根据业务需求进⾏完善

1. ⽤⼾登录⻚  :a. 根据⽤⼾名查询⽤⼾信息

2. 博客列表⻚  :a. 根据id查询user信息,b. 获取所有博客列表

3. 博客详情⻚:a. 根据博客ID查询博客信息,b. 根据博客ID删除博客(修改delete_flag=1)

4. 博客修改⻚:a. 根据博客ID修改博客信息

5. 发表博客:a. 插⼊新的博客数据
因此,根据以上的分析,我们就来实现业务层的开发

1.获取所有博客列表

    我们写出blogController(调用BlogService) 和 BlogService(调用BlogInfoMapper)

package com.example.blogsystem.Service;

import com.example.blogsystem.Mapper.BlogInfoMapper;
import com.example.blogsystem.Model.BlogInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class BlogService {
    @Autowired
    private BlogInfoMapper blogInfoMapper;
    public List<BlogInfo> getBlogList(){
        return  blogInfoMapper.queryBlogList();
    }
}



package com.example.blogsystem.Controller;

import com.example.blogsystem.Model.BlogInfo;
import com.example.blogsystem.Service.BlogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RequestMapping("/blog")
@RestController
public class BlogController {
    @Autowired
    private BlogService blogService;

    /*
    获取博客列表
     */
    @RequestMapping("/getList")
    public List<BlogInfo> getBlogList(){
        return blogService.getBlogList();
    }
}

然后,我们再来补充前端的代码        

接着我们运行程序,打开网页,看看是否能正常显示

我们发现能够正常的进行显示,但是显示的时间不是我们想要的,这里显示的是时间戳,我们需要显示创建的时间,详细看下面的官方参考文档

        

我们重写博客创建的时间   这里我们用到了y M d H m 在上表中对应的年 月 日 小时 分钟

package com.example.blogsystem.Utils;

import java.text.SimpleDateFormat;
import java.util.Date;

/*
日期工具类
 */
public class DateUtils {
        public  static String formatDate(Date date){
            SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm");
            return  simpleDateFormat.format(date);
        }

}

之后我们对BlogInfo里面的时间进行格式化

我们发现,日期已经正常显示

2.实现博客详情

博客详情就是点击查看全文,就能获取博客的详情

我们写出后端代码

测试后端代码也能拿到数据

接下来我们来写前端的代码

我们在script里面编写前端代码,完善前端代码,从服务器获取博客详情

 $.ajax({
            type:"get",
            url:"/blog/getBlogDetail"+location.search,
            success:function(result){
                if(result.code==200 && result.data!=null){
                    var blog=result.data;
                    $(".title").text(blog.title);
                    $(".date").text(blog.createTime);
                    $(".detail").text(blog.content);
                }
            }
        })
3.实现登录

在此之前,我们可以看到登录界面是需要输入用户名和密码才能登录

1.登录的逻辑

在此之前,我们学习了图书管理系统,我们实现登录的传统逻辑是

 • 输入账号和密码,然后后端进行校验

• 后端校验成功,存在session中,返回cookie

• 前端进行页面跳转,后续访问的时候,,会携带着cookie,也就是携带着sessionId,后端根据前面的取值从session中去取值,校验用户是否登录.

2.但我们传统的登录逻辑存在一些问题:

        session是存储在服务器的内存中,假如服务器进行重启的话,session就丢失了,用户就需要重新登录

         现在的服务一般都不是单机部署,一般都是多机器部署(俗称集群部署)

3,这里的话我们就来说一些关于服务器部署相关的问题

如上图,假如用户第一次登录请求被分配到了服务器1,session就存在了服务器1中

               用户第二次请求被分配到了服务器2中,服务器2中没有存session,校验失败,用户就需要重新登录,这显然是不合理的

            解决的办法:1.把session的值存在一个公用的机器中

                                 2.用token的方式就行存放 ,也可以说就是令牌技术

                                token:token就是一个带有信息的字符串,token是客服端进行访问时进行访问 时携带的相当于是身份标识的东西,不能伪造             

                    4. JWT令牌技术     

        我们这次的项目用JWT技术来实现登录校验

       JSON Web Token(JWT)是⼀个开放的⾏业标准(RFC7519),⽤于客⼾端和服务器之间传递安全可靠的信息.

其本质是⼀个token,是⼀种紧凑的URL安全⽅法.

                JWT的组成

                        JWT由三部分组成,每部分中间使⽤点(.)分隔,⽐如:aaaaa.bbbbb.cccc

                1• Header(头部)头部包括令牌的类型(即JWT)及使⽤的哈希算法(如                                      HMACSHA256或RSA)

                2• Payload(负载)负载部分是存放有效信息的地⽅,⾥⾯是⼀些⾃定义内                            容.⽐如:{"userId":"123","userName":"zhangsan"} ,

        也可以存在jwt提供的现场字段,⽐如exp(过期时间戳)等.

           此部分不建议存放敏感信息,因为此部分可以解码还原原始内容.

              3 • Signature(签名)此部分⽤于防⽌jwt内容被篡改,确保安全性.防⽌被篡改,⽽不是防⽌被解析.

          JWT之所以安全,就是因为最后的签名.jwt当中任何⼀个字符被篡改,整个令牌都会校验失败.就好⽐我们的⾝份证,之所以能标识⼀个⼈的⾝份,是因为他不能被篡改,⽽不是因为内容加密.(任何⼈都可以看到⾝份证的信息,jwt也是)

5.JWT令牌的实现

        1.引入依赖 

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
    </dependency>

<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> 
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

2.JWT的实现主要是两步,1. 生成token  2.验证token

        我们先在Test里面写一写

我们在登录账号的时候,系统会提示什么验证码或者密码在多久之前有效,我们写也可以这样子写

因此我们要生成安全密钥,设置时间,设置签名代码如下

package com.example.blogsystem;

import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.junit.jupiter.api.Test;

import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;


public class jwtUtilTest {
    //过期时间:30分钟:毫秒
    private  static  long expiration=30*60*1000;
    @Test
    public void  genToken(){
        //生成安全密钥
        Key key= Keys.hmacShaKeyFor(Decoders.BASE64.decode("zxcdddddddddddd"));
        Map<String,Object> claim=new HashMap<>();
        claim.put("id",1);
        claim.put("name","zhangsan");
        //设置有效期
      String  token= String.valueOf(Jwts.builder()
               .setClaims(claim)//自定义内容
                .setExpiration(new Date(System.currentTimeMillis()+expiration))//设置签发时间
                .signWith(key));//签名算法
    }
}

我们运行代码显示报错

这里提示我们签名长度不足256位

我们修改把key适当加长并且修改代码如下

package com.example.blogsystem;

import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.ToString;
import org.junit.jupiter.api.Test;

import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;


public class jwtUtilTest {
    //过期时间:30分钟:毫秒
    private  static final long expiration=30*60*1000;
    private  static  final String  secretKey=
            "dadadajhdjhadhjagjdghajhdjgadjajhdvjavdjhgafwdadawdadasdawdadasdawdasdawdawdadad";
    @Test
    public void  genToken(){
        //生成安全密钥
        Key key= Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretKey));
        Map<String,Object> claim=new HashMap<>();
        claim.put("id",1);
        claim.put("name","zhangsan");
        //设置有效期
      String  token= Jwts.builder()
               .setClaims(claim)//自定义内容
                .setExpiration(new Date(System.currentTimeMillis()+expiration))//设置签发时间
              .signWith(key)//签名算法
              .compact();


        System.out.println(token);
    }
}
                                                               

生成的结果如下

我们将生成的密钥放入Jwt官网上去,解码 出来就是我们输入的值

        

但是这种方法生成的密钥都要我们手动去输入,比较麻烦,我们就用另一种生成方法

  @Test
    public void  genKey(){
        //随机生成一个key
        SecretKey secretKey1= Keys.secretKeyFor(SignatureAlgorithm.HS256);
        String key = Encoders.BASE64.encode(secretKey1.getEncoded());
        System.out.println(key);
    }
}

6.校验信息

我们对生成的密钥进行校验,代码如下

@Test
    public  void parseToken(){
        String token="eyJhbGciOiJIUzM4NCJ9.eyJuYW1lIjoiemhhbmdzYW4iLCJpZCI6MSwiZXhwIjoxNzEwMjE5MjQwfQ" +
                ".wrrwSCQ4nBVHkwKnIKvU-EirSTBUiSzPpp5ba67CyKtbeEQ2rYr9QXCTywIOUxIc";
        JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();
        Claims body=build.parseClaimsJws(token).getBody();
        System.out.println(body);
    }
}

运行得出结果

解析成功,当我们修改一下token中的值,就会提示报错

这就是关于JWT的相关基础知识,接下来我们就要将运用到项目中

7.将JWT运用到项目中去

        以前的登录流程:1.根据用户名和密码,验证密码是否正确

                                      2.如果密码正确,存储session

                                      3.后续访问的时候,携带cookie(sessionId)

           现在使用token的方式:

                                         1.根据用户名和密码,验证密码是否正确

                                        2.如果密码正确,后端生成token,返回给前端(放在cookie中,或者本                                               地的存储中)

                                        3.后续访问时(一般放在http请求的header中),携带token,后端校验                                              token的合法性

代码如下

package com.example.blogsystem.Controller;

import com.example.blogsystem.Model.Result;
import com.example.blogsystem.Model.UserInfo;
import com.example.blogsystem.Service.UserService;
import com.example.blogsystem.Utils.JwtUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RequestMapping("/user")
@RestController
public class UserController {
    @Autowired
    private UserService userService;
    @RequestMapping("/login")
    public Result login(String userName, String password){
        //1、进行参数校验
        //2.密码校验
        //3.生成token并返回
        if(!StringUtils.hasLength(userName)||!StringUtils.hasLength(password)){
            return Result.fail("用户名或密码为空");
        }
        //获取数据库中的密码
        UserInfo userInfo=userService.queryByName(userName);
        if(userInfo==null||userInfo.getId()<0){
            return Result.fail("用户不存在");
        }
        if(!password.equals(userInfo.getPassword())){
            return Result.fail("密码错误");
        }

        //生成token并返回

        Map<String,Object> claim=new HashMap<>();
        claim.put("id",userInfo.getId());
        claim.put("name",userInfo.getUserName());
        String token= JwtUtils.genToken(claim);
        return Result.success(token);




    }
}

    我们测试接口是否能返回结果,如下图结果正常返回

接下来我们来完善前端的代码:

依然用ajax来获取值

写好之后我们进入登录界面,发现能够实现登录,我们在后端发现user_token已经存入

我们写好了登录逻辑,接下来就是强制登录

8.强制登录

       按照惯例我们还是需要来写出强制登录的代码,必须要登录才能访问博客主页

我们的拦截器需要去继承HandlerInterceptor这个接口 然后再重写这个接口中的preHandle方法

这次我们需要校验header中token的合法性,接下来进行登录校验

因此我们还需要对客户端的token进行解析来验证token的合法性

接下来继续继续登录校验,登录校验的原理如下

代码如下
package com.example.blogsystem.config;

import com.example.blogsystem.Utils.JwtUtils;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Configuration
public class LoginInterceptor implements HandlerInterceptor{
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //进行用户登录校验
        //1.从header中获取token
        //2.验证token
        String token=request.getHeader("user_token");
        //约定前端发送请求时,header中发送一个user_token的值
        log.info("从header中获取token"+token);
        Claims claims= JwtUtils.parseToken(token);
        if(claims==null){
            //token为空,不合法 
            response.setStatus(401);
            return false;
        }
        
        return true;

    }
}

 

我们的token验证完毕后,我们需要把拦截器添加到服务上,因此我们还需要写一个WebConfig类

我们拦截器需要排除掉不需要拦截的一些静态代码

package com.example.blogsystem.config;

import org.apache.ibatis.annotations.Select;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Arrays;
import java.util.List;

@Configuration
public class WebConfig  implements WebMvcConfigurer {
    private  static  final List<String>excludePath= Arrays.asList(
            "/user/login",
            "/**/*.html",
            "/pic/**",
            "/js/**",
            "/css/**",
            "/blog-editormd/**");
    @Autowired
    private  LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor).addPathPatterns("/**")
                .excludePathPatterns(excludePath);

    }
}

我们想要在header中每一次发送都带有token,就需要在commen.js中写出ajaxSend

$(document).ajaxSend(function(e,xqr,op){
    var token=localStorage.getItem("user_token");
    xqr.setRequestHeader("user_token",token);
});

              我们运行程序,直接进入主页,跳出弹框:用户未登录

因此部署token的大概流程如下所示

4.实现显示用户信息

我们进入主页时,显示的信息为用户的信息,所以现在我们要在登录时实现登录用户的信息

获取用户信息由两种方式可以实现 

    1.如果页面的信息较少,且是固定不变的内容的话,就可以把信息存储在token中,直接从token中获取,但是不建议使用这种方法

   2.从token中获取id,根据用户id 获取用户信息

 //获取用户登录信息
    @RequestMapping("/getUserInfo")
    public UserInfo getUserInfo(HttpServletRequest httpServletRequest){
        //1.从token中获取用户id
        //2.根据用户id,获取用户信息
        String token=httpServletRequest.getHeader("user_token");
        Integer userId=JwtUtils.getUserIdFromToken(token);
        if(userId==null){
            return null;
        }
        return userService.queryById(userId);
    }

    //获取当前作者信息
    @RequestMapping("getAutoInfo")
    public UserInfo getAutoInfo(Integer blogId){
        if(blogId==null||blogId<0){
            return null;
        }

        return  userService.getAuthorInfo(blogId);
    }


}

         

我们需要再登录后用户前端的个人信息也得到更新

这样写之后,我们的界面名称也发生了变化

然后我们继续编写博客详情页

我们点进去详情之后,详情页的名字也发生了变化

我们可以发现博客列表页和博客的详情页前端代码几乎是一样的,只有url不一样,因此,我们可以精简代码,把代码放到common.js中去,只修改url即可

blog_list就变成了如下,以后修改只用修改common中的即可

5.实现用户的退出

用户在主页和博客详情页都有实现用户的退出,因此,我们也把退出的程序写在common中

6.实现发布博客
       先约定先后端的接口

我们先编写后端代码
@RequestMapping("/add")
    public Result publishBlog(String title , String content, HttpServletRequest httpServletRequest){
        //从token中获取user_id
        String token=httpServletRequest.getHeader("user_token");
        Integer userId= JwtUtils.getUserIdFromToken(token);

        if(userId==null||userId<0){
            return  Result.fail("用户未登录",false);
        }
        //插入博客中去
        BlogInfo blogInfo=new BlogInfo();
        blogInfo.setUserId(userId);
        blogInfo.setTitle(title);
        blogInfo.setContent(content);
        blogService.insertBlog(blogInfo);
        return  Result.success("true");
    }
再编写前端代码
   function submit() {
        $.ajax({
            type:"post",
            url:"/blog/add",
            data:{
                title:$("#title").val(),
                content:$("#content").val()
            },
            success:function(result){
                if(result.code==200){
                    alert("发布成功!");
                    location.href="blog_list.html";
                }else{
                    alert(result.error);
                }
            }
        });
        }
实现编辑删除功能
        实现登录用户和博客详情页统一

现在我们发现不是作者本人也可以对博客进行编辑和删除,这是不合理的,合理的逻辑是当登录用户和作者是同一个人的时候,才可以编辑/删除

        这里的逻辑是,在博客详情页,判断是否显示 编辑/删除  判断条件:登录用户==博客详情页的用户

 @RequestMapping("/getBlogDetail")
    public  BlogInfo getBlogDetail(Integer blogId,HttpServletRequest httpServletRequest){
            BlogInfo blogInfo=blogService.getBlogDetail(blogId);
            //判断作者是否为登录用户
        String token=httpServletRequest.getHeader("user_token");
        Integer userId= JwtUtils.getUserIdFromToken(token);
        if(userId!=null&&userId==blogInfo.getUserId()){
            blogInfo.setLoginUser(true);
        }
        return blogInfo;
    }

这里就是完善blog_detail的代码,让其能判断登录用户==博客详情页用户

接下来完善前端代码

我们加入判断是否显示编辑和删除按钮

接下来就是实现编辑和删除功能

  约定前后端接口

后端代码如下

@RequestMapping("/update")
    public  boolean updateBlog(Integer id,String title,String content,HttpServletRequest httpServletRequest) {
    //String token = httpServletRequest.getHeader("user_token");
    //Integer userId = JwtUtils.getUserIdFromToken(token);
    BlogInfo blogInfo = new BlogInfo();
    //Integer userid=blogInfo.getId();
   // if (userId == userid) {
        blogInfo.setTitle(title);
        blogInfo.setContent(content);
        //根据id去更新数据
        blogInfo.setId(id);
        blogService.updateBlog(blogInfo);
    //}

    return true;
}
    @RequestMapping("/delete")
    public  boolean deleteBlog(Integer blogId,HttpServletRequest httpServletRequest){
        //String token=httpServletRequest.getHeader("user_token");
        //Integer userId= JwtUtils.getUserIdFromToken(token);
        //BlogInfo blogInfo=new BlogInfo();
        //Integer id=blogInfo.getId();
        //if(userId==id) {
            blogService.delete(blogId);
        //}
        return  true;
    }

我们放入postman测试后端接口,成功响应

接下来我们来编写前端代码

更新博客

  function submit() {
           $.ajax({
            type:"post",
            url:"/blog/update",
            data:{
                id:$("#blogId").val(),
                title:$("#title").val(),
                content:$("#content").val()
            },
            success:function(result){
                if(result.code==200&&result.data==true){
                    alert("更新成功");
                    location.href="blog_list.html";
                }
            }
           });
        }

删除博客

//删除博客
            function deleteBlog(blogId){
               $.ajax({
                type:"post",
                url:"/blog/delete"+location.search,
                success:function(result){
                    if(result.code==200&&result.data==true){
                        alert("删除成功");
                        location.href="blog_list.html";
                    }
                }
               });
            }

7.加密

             在MySQL数据库中,我们常常需要对密码,⾝份证号,⼿机号等敏感信息进⾏加密,以保证数据的安全性.如果使⽤明⽂存储,当⿊客⼊侵了数据库时,就可以轻松获取到⽤⼾的相关信息,从⽽对⽤⼾或者企业造成信息泄漏或者财产损失.

     ⽬前我们⽤⼾的密码还是明⽂设置的,为了保护⽤⼾的密码信息,我们需要对密码进⾏加密

        加密算法分类:密码算法主要分为三类  对称密码算法,⾮对称密码算法,摘要算法

1. 对称密码算法是指加密秘钥和解密秘钥相同的密码算法.常⻅的对称密码算法有:AES,DES,3DES,RC4,RC5,RC6等.

2. ⾮对称密码算法是指加密秘钥和解密秘钥不同的密码算法.该算法使⽤⼀个秘钥进⾏加密,⽤另外⼀个秘钥进⾏解密.

◦ 加密秘钥可以公开,⼜称为公钥 ◦ 解密秘钥必须保密,⼜称为私钥

  常⻅的⾮对称密码算法有:RSA,DSA,ECDSA,ECC等

3. 摘要算法:是指把任意⻓度的输⼊消息数据转化为固定⻓度的输出数据的⼀种密码算法.摘要算法是不可逆的,也就是⽆法解密.通常⽤来检验数据的完整性的重要技术,即对数据进⾏哈希计算然后⽐较摘要值,判断是否⼀致.常⻅的摘要算法有:MD5,SHA系列(SHA1,SHA2等),CRC(CRC8,CRC16,CRC32)

加密思路

博客系统中,我们采⽤MD5算法来进⾏加密.

问题:虽然经过MD5加密后的密⽂⽆法解密,但由于相同的密码经过MD5哈希之后的密⽂是相同的,当存储⽤⼾密码的数据库泄露后,攻击者会很容易便能找到相同密码的⽤⼾,从⽽降低了破解密码的难度.因此,在对⽤⼾密码进⾏加密时,需要考虑对密码进⾏包装,即使是相同的密码,也保存为不同的密⽂.即使⽤⼾输⼊的是弱密码,也考虑进⾏增强,从⽽增加密码被攻破的难度.

解决⽅案:采⽤为⼀个密码拼接⼀个随机字符来进⾏加密,这个随机字符我们称之为"盐".假如有⼀个加盐后的加密串⿊客通过⼀定⼿段这个加密串,他拿到的明⽂并不是我们加密前的字符串,⽽是加密前的字符串和盐组合的字符串,这样相对来说⼜增加了字符串的安全性.

解密流程(校验流程):MD5是不可逆的,通常采⽤"判断哈希值是否⼀致"来判断密码是否正确.如果⽤⼾输⼊的密码,和盐值⼀起拼接后的字符串经过加密算法,得到的密⽂相同,我们就认为密码正确(密⽂相同,盐值相同,推测明⽂相同)

代码如下采用MD5加密的方式

生成随机盐值,并把盐值中的-去掉

再把盐值和明文的密码得到密文

代码如下

package com.example.blogsystem.Utils;

import org.springframework.util.DigestUtils;

import java.util.UUID;

public class SecurityUtil {
    /*
    对密码进行加密
     */
    public   static  String encrypt(String password) {
        // 每次⽣成内容不同的,但⻓度固定 32 位的盐值
        //生成随机盐值
        String salt = UUID.randomUUID().toString().replace("-","");
        System.out.println(salt);
        //加密  盐值+明文
        String securityPassword = DigestUtils.md5DigestAsHex((salt+password).getBytes());
        //数据库中存储   盐值+密文
        return salt+securityPassword;

    }

    public static void main(String[] args) {
        System.out.println(encrypt("123456"));
    }

    /*
    校验
     */

    public static boolean verify(String inputPassword, String sqlPassword){
        //取出盐值
        if (sqlPassword ==null || sqlPassword.length()!=64){
            return false;
        }
        String salt = sqlPassword.substring(0,32);
        //得到密文
        String securityPassword = DigestUtils.md5DigestAsHex((salt+inputPassword).getBytes());
        return (salt+securityPassword).equals(sqlPassword);
    }
}

我们最后再到usercontroller中修改登录逻辑

再到数据库中去修改密码为明文加盐值

最后程序完成了

8.我们讲了如何进行加密,接下来就开始讲如何进行多平台配置

        我们在sql上的账号和密码假如是一个,那我们在云服务器上的账号和密码又是另一个,我们在不同平台想用的话就需要来回的该密码配置,这样就会很麻烦,因此我们的解决方法方法就是根据不同的平台去设置不同的配置文件

配置如下

我们要想调用哪个就选那个,在pom中我们需要添加如下

  <profiles>
        <profile>
            <id>dev</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties> <profile.Name>dev</profile.Name> </properties>
        </profile>

        <profile>
            <id>prod</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties> <profile.Name>prod</profile.Name> </properties>
        </profile>
    </profiles>

3程序部署

我们完成了程序的开发,接下来想要其他人能访问我们的程序,我们就需要进行部署

1.搭建java部署环境

        1.apt

      apt(AdvancedPackagingTool),Linux软件包管理⼯具.⽤于在UbuntuDebian和相关Linux发⾏版上安装、更新、删除和管理deb软件包.

     apt常⽤命令

                列出所有软件包 : apt list              

                这个命令输出所有包的列表,内容⽐较多,可以使⽤grep命令过滤输出.:

                apt list |grep "java"

                 更新软件包数据库:sudo apt-get update

                如果切换到root⽤⼾,命令前就不需要加sudo了

                 切换root:sudo su

                2.jdk

                 我们需要安装jdk,通过指令  apt list |grep "jdk"查找jdk 

                再通过sudo apt install openjdk-8-jdk进行安装

使用java -version查看是否安装完成

配置环境

        3.mysql

        1.使用apt安装MySQL

                #查找安装包

                apt list |grep "mysql-server"

                # 安装mysql

                sudo apt install mysql-server

登录到mysql

mysql -uroot -p

查看MySQL的状态

  sudo systemctl status mysql

进入mysql :sudo mysql

利用alter user修改密码 

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456';

2.部署web项目到linux

        1.什么是部署


        ⼯作中涉及到的"环境"

        • 开发环境:开发⼈员写代码⽤的机器.

        • 测试环境:测试⼈员测试程序使⽤的机器.

        • ⽣产环境(线上环境):最终项⽬发布时所使⽤的机器.对稳定性要求很⾼.

               把程序安装到⽣产环境上,这个过程称为"部署".也叫"上线".

               ⼀旦程序部署成功,那么这个程序就能被外⽹中千千万万的普通⽤⼾访问到.换句话说,如果程序有BUG,这个BUG也就被千千万万的⽤⼾看到了.

部署过程⾄关重要,属于程序开发中最重要的⼀环.⼀旦部署出现问题,极有可能导致严重的事故(服务器不可⽤之类的).

为了防⽌部署出错,⼀般公司内部都有⼀些⾃动化部署⼯具(如Jenkins等).当前我们先使⽤⼿⼯部署的⽅式来完成部署

2将jar包上传到linux上

  我们在idea中进行打包完成后,就把jar包放入linux中,直接拖入或者使用命令即可

Xshell可以直接拖动⽂件到窗⼝,达到上传⽂件的⽬的,如果使⽤其他客⼾端,不⽀持⽂件的上传,需要借助lrzsz命令

上传⽂件:sz filename

下载⽂件 : rz

运⾏程序:nohup java -jar blog-spring-0.0.1-SNAPSHOT &

nohup:后台运⾏程序.⽤于在系统后台不挂断地运⾏命令,退出终端不会影响程序的运⾏

3.运行程序

  我们输入指令  java -jar Blog-System-0.0.1-SNAPSHOT.jar  启动程序

启动成功 接下来我们在设置云服务器开放相应的端口号即可

之后程序正常登录

        

4.程序后台启动

        我们在关闭会话之后,网页就不能访问了,所以我们要让程序在后台启动

        程序后台启动: nohup  &

5.杀掉进程

  我们后台运行的进程如果想结束掉进程

就先ps -ef |grep (关键字) 查找相关的进程

然后再kill -9 进程id 杀掉进程 

3.跟踪日志

                在程序的运行中,查看日志是很重要的,我们如何在linux中查看日志呢,我们首先找到日志文件 然后使用指令    tail -f (要跟踪的日志) 

日志就会随着程序的操作而更新

1.过滤日志

     我们的程序在执行的过程中有很多日志,假如我们只想看ERROR错误日志,我们就需要使用过滤来过滤日志

                

                

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue3项目初始化

初始化项目newsapp VSCode 打开终端&#xff0c;newsapp项目目录&#xff0c;可自定义 vue create newsapp 有提示“因为在此系统上禁止运行脚本”的话&#xff0c;请执行 set-ExecutionPolicy RemoteSigned 执行后再重复执行vue create newsapp 注意选择Vue 3版本 测试项…

【案例分析】入职第一天,如何让同事对我刮目相看

背景 在办理入职的第一天&#xff0c;遇到测试同事无奈且慌张的报出一个问题&#xff1a;拷机过程中&#xff0c;stTsp进程重启了。可能因为大家都比较忙&#xff0c;也可能因为面试过程中&#xff0c;我说自己比较喜欢解决问题。领导就让我帮忙一起看看。 呃&#xff0c;此时…

配置java8和java11以及输入version命令没反应问题

电脑重置重新安装java8和java11记录一下供自己观看 安装过程掠过我自己能看懂就行 需要记录一下 因为JDK11以后Oracle把JRE集成到了JDK中&#xff0c;在安装JDK11及更高版本的JDK时&#xff0c;默认是不会自动安装JRE的。在jdk11的安装目录下打开命令行窗口或者shift鼠标右键…

异步处理 (vue async和await)

出现这种情况可以是加载顺序的问题&#xff0c;加载没有完成就是显示数据了 试试 async和await async beforeOpen(done, type) {if (["edit", "view"].includes(type)) {await getDetail(this.form.id).then((res) > {this.form res.data.data;conso…

了解Spring:Java开发的利器

Spring是一款开源的轻量级Java开发框架&#xff0c;旨在提高开发人员的效率和系统的可维护性。本文将介绍Spring的基本概念、使用优势、设计模式以及与Spring MVC和Spring Boot的关联。 什么是Spring&#xff1f; Spring是一款开源的轻量级Java开发框架&#xff0c;它由多个模…

购买腾讯云服务器需要多少钱?价格表查询

腾讯云服务器多少钱一年&#xff1f;61元一年起。2024年最新腾讯云服务器优惠价格表&#xff0c;腾讯云轻量2核2G3M服务器61元一年、2核2G4M服务器99元一年可买三年、2核4G5M服务器165元一年、3年756元、轻量4核8M12M服务器646元15个月、4核16G10M配置32元1个月、312元一年、8核…

APP信息收集思路总结(反代理,反虚拟机,反证书校验思路整理)

前言 本文是我在学习过程中的总结&#xff0c;希望可以被指导提议指正。 APP概况 app跟一个网站很像。 网站分为前端后端。 app就好像网站的前端一样&#xff0c;app不需要浏览器&#xff0c;而前端需要浏览器。 他们都需要服务器&#xff0c;也就是说&#xff0c;进行we…

bevformer转模型经验(需要时序tranformer所有模型都可以参考)

先上bevformer的网络结构图 不难发现&#xff0c;他有两个输入。当前的环视图和历史bev。历史bev是通过历史环视图生成的。也就是说在生成bev特征提取模型这部分被使用了两次。在装模型时候&#xff0c;需要作以下工作&#xff1a; 1 bev特征提取模型单独提出来&#xff0c;转…

win多开微信

有时候需要在win下多开微信&#xff0c;但是微信又不支持这个功能。 正常情况下&#xff0c;当微信弹出登录对话框时&#xff0c;再次点击微信exe&#xff0c;此时不会再弹出一个新的微信对话框&#xff0c;估计微信是做了只弹一个窗的判断。但是&#xff0c;有时点击微信时&a…

哪本书最了解孩子?跟《米小圈上学记》一起做孩子的“引路人”!

孩子是发展中的人&#xff0c;需要家长的陪伴&#xff0c;孩子的身心发展是有规侓的&#xff0c;是处于发展过程中的人。我们要学会尊重孩子的发展&#xff0c;从兴趣出发&#xff0c;关注孩子的成长。但是&#xff0c;家长不可能无时无刻都能陪在孩子身边&#xff0c;他需要一…

es bulk批量操作简单实例

&#xff08;1&#xff09;定义 bulk允许在单个步骤中进行多次create、index、update或delete请求。 bulk与其他的请求体格式稍有不同&#xff0c;如下所示&#xff1a; { action: { metadata }}\n { request body }\n { action: { metadata }}\n { request body …

element-plus中el-table利用复选框删除当前行

思路&#xff1a;1.利用复选框的selection-change事件来监听复选框发生的变化&#xff0c;触发该事件 2.给删除添加点击事件&#xff0c;定义一个空数组&#xff0c;通过forEach()方法遍历tableData数据&#xff0c;使用filter()方法来过滤掉未选中行数&#xff0c;将过滤出来的…

java-基于springboot+vue实现的旅游信息管理系统功能介绍

开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot 前端&#xff1a;Vue、ElementUI 关键技术&#xff1a;springboot、SSM、vue、MYSQL、MAVEN 数据库工具&#xff1a;Navicat、SQLyog 项目关键技术 1、JSP技术 JSP(Java…

苍穹外卖笔记

苍穹外卖 DAY01nginx反向代理MD5加密yapi进行接口导入Swagger介绍 DAY02新增员工需求分析和设计写相关代码测试(1. 后端文档测试 2. 前后端联调代码完善 员工分页查询DAY01 02涉及到的知识 DAY01 nginx反向代理 MD5加密 拓展&#xff1a;spring security jwt 提供了更强大灵…

新能源汽车充电桩消防安全视频智能可视化监管建设方案

一、方案背景 据应急管理部门统计公布的数据显示&#xff0c;仅2023年第一季度&#xff0c;新能源汽车自燃率就上涨了32%&#xff0c;平均每天就有8辆新能源汽车发生火灾&#xff08;含自燃&#xff09;。在已查明起火原因中&#xff0c;58%源于电池问题&#xff0c;19%源于碰…

摘录笔记——2024年3月22日

目录 一、背景 1.1 新人的选择困局 1.2 高人才密度环境下普通员工的成长效率困局 1.3 业务发展和个人成长的二元对立困局 1.4 中年打工人低费效比引发的职场生涯终结困局 二、人的本质 2.1 人的本质的定义 2.2 由“人的本质”引出的几个关键过程 2.2.1 认知指引实践&a…

常用类一(包装类)

目录 基本数据类型的包装类 包装类基本知识 包装类的用途 自动装箱和拆箱 自动装箱&#xff1a; 自动拆箱&#xff1a; 包装类的缓存问题 基本数据类型的包装类 八种基本数据类型并不是对象&#xff0c;为了将基本类型数据和对象之间实现互 相转化&#xff0c;JDK 为每一…

【新手教程】mmselfsup训练教程及常见报错处理

mmselfsup教程 1.安装mmselfsup2.了解文件结构与配置3.训练常见报错1.报错&#xff1a;FileNotFoundError: [Errno 2] No such file or directory:data/imagenet/train/./train/n04311004/images/n04311004_194.JPEG2.报错&#xff1a;报错ImportError: /mmcv/_ext.cpython-38-…

面试官:小伙子知道synchronized的优化过程吗?我:嘚吧嘚吧嘚,面试官:出去!

写在开头 面试官&#xff1a;小伙子&#xff0c;多线程中锁用过吗&#xff1f; 我&#xff1a;那是自然&#xff01; 面试官&#xff1a;那你知道synchronized的优化吗&#xff1f; 我&#xff1a;synchronized作为重锁&#xff0c;开销大&#xff0c;在早期不被推荐使用&…

蓝桥杯练习04学生成绩统计

学生成绩统计 介绍 随着大数据的发展&#xff0c;数据统计在很多应用中显得不可或缺&#xff0c;echarts作为一款基于JavaScript的数据可视化图表库&#xff0c;也成为了前端开发的必备技能&#xff0c;下面我们一起来用echarts开发一个学生数据统计的柱形图。 准备 开始答…