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

news2024/9/23 20:11:28

在之前的学些中,我们掌握了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错误日志,我们就需要使用过滤来过滤日志

                

                

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

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

相关文章

基于傅里叶描述子的手势动作识别,Matlab实现

博主简介&#xff1a; 专注、专一于Matlab图像处理学习、交流&#xff0c;matlab图像代码代做/项目合作可以联系&#xff08;QQ:3249726188&#xff09; 个人主页&#xff1a;Matlab_ImagePro-CSDN博客 原则&#xff1a;代码均由本人编写完成&#xff0c;非中介&#xff0c;提供…

ubuntu22.04物理机双系统手动分区

ubuntu22.04物理机双系统手动分区 文章目录 ubuntu22.04物理机双系统手动分区1. EFI系统分区2. 交换分区3. /根分区4. /home分区分区后的信息 手动分区顺序&#xff1a;EFI系统分区(/boot/efi)、交换分区(/swap)、/根分区、/home分区。 具体参数设置&#xff1a; 1. EFI系统分…

02. 【Android教程】开发环境搭建

在学习 Android 应用开发之前&#xff0c;我们先要完成环境的搭建&#xff0c;它将帮助我们将 Java 代码编译打包生成最终的 Android 安装包。本教程在 Mac 下完成安装&#xff0c;Windows 和 Linux 步骤类似&#xff0c;不同之处会着重区分。 1. 文件清单 Java SE Developmen…

JVM的知识

什么是JVM 1.JVM&#xff1a; JVM其实就是运行在 操作系统之上的一个特殊的软件。 2.JVM的内部结构&#xff1a; &#xff08;1&#xff09;因为栈会将执行的程序弹出栈。 &#xff08;2&#xff09;垃圾99%的都是在堆和方法区中产生的。 类加载器&#xff1a;加载class文件。…

芯片中小公司ERP系统的业务流程:揭秘数字化管理的新篇章

随着信息技术的飞速发展&#xff0c;ERP(企业资源规划)系统已成为众多企业实现数字化管理的重要工具。对于芯片中小公司而言&#xff0c;ERP系统更是提升运营效率、优化资源配置的关键所在。那么&#xff0c;芯片中小公司的ERP系统究竟是如何运作的呢?让我们一同揭开其业务流程…

Spatialite坐标投影并计算面积

将坐标转为WGS_1984_UTM_Zone_48N&#xff08;32648&#xff09;后再计算其面积&#xff1a; -- 转换坐标系并计算面积&#xff08;平方米&#xff09; SELECT ST_Area(ST_Transform(GeomFromText(POLYGON((106.763 26.653, 106.763 26.626, 106.815 26.625, 106.809 26.666, …

我们使用 Postgres 构建多租户 SaaS 服务时踩的坑

原文 Our Multi-tenancy Journey with Postgres Schemas and Apartment。这篇和之前发出的「如何使用 Postgres 对一个多租户应用分片」相呼应。 多租户 (Multip-tenancy) 是当下的热门话题。我对多租户应用程序的定义是一个能够服务于多个客户的软件系统&#xff0c;每个客户都…

有名的爬虫框架 colly 的特性及2个详细采集案例

一. Colly概述 前言&#xff1a;colly 是 Go 实现的比较有名的一款爬虫框架&#xff0c;而且 Go 在高并发和分布式场景的优势也正是爬虫技术所需要的。它的主要特点是轻量、快速&#xff0c;设计非常优雅&#xff0c;并且分布式的支持也非常简单&#xff0c;易于扩展。 框架简…

javaSSM游泳馆日常管理系统IDEA开发mysql数据库web结构计算机java编程maven项目

一、源码特点 IDEA开发SSM游泳馆日常管理系统是一套完善的完整企业内部系统&#xff0c;结合SSM框架和bootstrap完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;MAVEN方式加载&#xff0c;系统具有完整的源代码和…

疫情居家办公OA系统设计与实现| Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java&#xff0c;…

day04套餐管理模块所有业务功能代码开发

目录 1. 新增套餐1.1 需求分析和设计1.2 代码实现1.2.1 DishController1.2.2 DishService1.2.3 DishServiceImpl1.2.4 DishMapper1.2.5 DishMapper.xml1.2.6 SetmealController1.2.7 SetmealService1.2.8 SetmealServiceImpl1.2.9 SetmealMapper1.2.10 SetmealMapper.xml1.2.11…

shell脚本入门练习(非常详细)零基础入门到精通,收藏这一篇就够了

【脚本1】打印形状 打印等腰三角形、直角三角形、倒直角三角形、菱形 #!/bin/bash \# 等腰三角形 read \-p "Please input the length: " n for i in \seq 1 $n\ do for ((j\$n;j>i;j--)) do echo \-n " " done for m in \seq 1 $i\ do…

希尔伯特-黄变换(Hilbert-Huang Transform, HHT)详解

目录 经验模态分解&#xff08;EMD&#xff09; 希尔伯特谱分析&#xff08;HSA&#xff09; 定义 连续时信号的Hilbert变换定义 离散时信号的Hilbert变换定义 解析信号定义&#xff1a; 解析信号的傅里叶变换 解析信号的重要意义 解析信号的属性 希尔伯特--黄变换&#xff08;…

LabVIEW电动汽车直流充电桩监控系统

LabVIEW电动汽车直流充电桩监控系统 随着电动汽车的普及&#xff0c;充电桩的安全运行成为重要议题。通过集成传感器监测、单片机技术与LabVIEW开发平台&#xff0c;设计了一套电动汽车直流充电桩监控系统&#xff0c;能实时监测充电桩的温度、电压和电流&#xff0c;并进行数…

Geohash编码

1. 简介 地理位置&#xff08;经纬度坐标对&#xff09;编码为字母数字串&#xff0c;将空间分为网格形状每个网格使用一个编码&#xff0c;是Z阶曲线的众多应用之一。 2. 编码原理 &#xff08;1&#xff09; 首先根据区域划分的精度大小选择Geohash的字符串的长度&#xf…

[DDD] ValueObject的一种设计落地及应用

目录 前言一、ValueObject二、设计2.1 接口2.2 单一值ValueObject2.3 单一字符串ValueObject 三、实现3.1 示例3.1.1 PhoneNumber3.1.2 SocialCreditCode 四、使用4.1 异常处理4.2 Json 反/序列化4.2.1 请求体4.2.2 HTTP接口4.2.3 用例 4.3 JPA/MyBatis4.3.1 Converter或TypeHa…

HarmonyOS实战开发-如何使用首选项能力实现一个简单示例。

介绍 本篇Codelab是基于HarmonyOS的首选项能力实现的一个简单示例。实现如下功能&#xff1a; 创建首选项数据文件。将用户输入的水果名称和数量&#xff0c;写入到首选项数据库。读取首选项数据库中的数据。删除首选项数据文件。 最终效果图如下&#xff1a; 相关概念 首选…

第二证券|基本面向好预期强化 全球资本加紧布局A股

开年以来&#xff0c;在我国经济上升向好的态势持续稳固增强的大布景下&#xff0c;结合各方努力&#xff0c;A股商场企稳上升痕迹明显。受一系列稳定商场预期政策出台的加持&#xff0c;全球本钱正在加速布局A股商场。 业界人士指出&#xff0c;当时我国本钱商场依然具有明显…

QT(6.5) cmake构建C++编程,调用python (已更新:2024.3.23晚)

一、注意事项 explicit c中&#xff0c;一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数)&#xff0c;承担了两个角色&#xff0c;构造器、类型转换操作符&#xff0c; c提供关键字explicit&#xff0c;阻止转换构造函数进行的隐式转换的发生&#…

jvm底层

逐步细化 静态链接&#xff1a;静态方法(符号引用)替换为内存指针或者句柄直接引用) 动态链接&#xff1a;程序期间将符号引用替换为直接引用 对象头&#xff1a; 指针压缩&#xff1a; -XX:UseCompressedOops 开启指针压缩 减少内存消耗&#xff1b;大指针在主内存 缓存间移…