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

在之前的学些中,我们掌握了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 {@Overridepublic boolean supports(MethodParameter returnType, Class converterType) {return true;}@SneakyThrows@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if(body instanceof Result){return body;}if (body instanceof String){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 {@ExceptionHandlerpublic 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 {@Autowiredprivate 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 {@Autowiredprivate 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;@Testpublic 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";@Testpublic 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官网上去,解码 出来就是我们输入的值

        

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

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

6.校验信息

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

@Testpublic  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 {@Autowiredprivate 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{@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//进行用户登录校验//1.从header中获取token//2.验证tokenString 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/**");@Autowiredprivate  LoginInterceptor loginInterceptor;@Overridepublic 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_idString 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.rhkb.cn/news/284728.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

覃超老师 算法面试通关40讲

教程介绍 无论是阿里巴巴、腾讯、百度这些国内一线互联网企业&#xff0c;还是 Google、Facebook、Airbnb 等硅谷知名互联网公司&#xff0c;在招聘工程师的过程中&#xff0c;对算法和数据结构能力的考察都是重中之重。本课程以帮助求职者在短时间内掌握面试中最常见的算法与…

这个世界会好吗

1918年11月7日&#xff0c;梁漱溟的父亲梁济正准备出门&#xff0c;遇到漱溟&#xff0c;二人谈起关于欧战的一则新闻。“世界会好吗&#xff1f;”父亲最后问道。儿子回答&#xff1a;“我相信世界是一天一天往好里去的。” “能好就好啊&#xff01;”父亲说罢就离开了家。 三…

使用切片技术从点云中测量树木胸径DBH

胸高直径(DbH)是树木库存调查的重要树木指标和元数据,它可以指示树木的相对年龄、大小和林业发展阶段。在MMS(移动测绘系统)LiDAR点云中,集合近年来一直在快速增长。在这项试点研究中,我们将通过对路边的目标树群进行切片,将这些收集到的点云用于城市林业和遥感。 具有…

vector类详解及重要函数实现

&#x1fa90;&#x1fa90;&#x1fa90;欢迎来到程序员餐厅&#x1f4ab;&#x1f4ab;&#x1f4ab; 今日主菜&#xff1a;vector类 主厨&#xff1a;邪王真眼 所属专栏&#xff1a;c专栏 主厨的主页&#xff1a;Chef‘s blog 坚持下去&#xff0c;成功不是目的&a…

抖音IP属地怎么更改

抖音是一个非常受欢迎的短视频平台&#xff0c;吸引了无数用户在上面分享自己的生活和才艺。然而&#xff0c;随着快手的火爆&#xff0c;一些用户开始担心自己的IP地址会被他人获取&#xff0c;引起个人隐私风险。那么&#xff0c;抖音用户又该如何更改到别的地方呢&#xff1…

babyos 学习记录

宏定义头文件 将一个宏定义取不同的数据到不同的数组中&#xff1b; 侵入式链表 struct list_head { struct list_head *next, *prev; }; // 添加&#xff08;list_add_tail/list_add&#xff09;、删除、查找 xx.h // 定义一个用于链表管理的结构体 typedef sturct{ xxx …

2024-03-24 思考-MBTI-简要记录

摘要: 2024-03-24 思考-MBTI-简要记录 MBTI16型人格: MBTI16型人格在人格研究和评价中得到了广泛的应用。MBTI是一种基于瑞士心理学家荣格在理论基础上发展起来的人格分类工具。为了准确判断个人的心态偏好&#xff0c;将每个人分为16种不同的人格类型。这种分类方法不仅为我们…

Websocket + Vue使用

这里有一篇文档可以参考一下> 闪现 POM文件 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId><version>2.7.0</version> </dependency> WebSocketConf…

手撕算法-删除有序数组中的重复项

描述 很简单&#xff0c;就是&#xff0c;遇到重复的&#xff0c;只留一个&#xff0c;保存在数组的左半边。如&#xff1a;[0,0,1,1,1,2,2,3,3,4]变为[0,1,2,3,4] 分析 使用双指针。slow指针代表没重复的数应该放置的位置&#xff0c;fast表示遍历的不重复数字的位置&…

【Linux更新驱动、cuda和cuda toolkit】

目录 1. 更新显卡驱动1.1. 查看当前显卡驱动版本1.2. 删除原始显卡驱动1.3. 删除CUDA Toolkit1.4. 在NVIDIA官网找到2080Ti对应的最新驱动程序 2. 更新CUDA Toolkit2.1. 下载CUDA Toolkit2.2. 安装.run2.3. 添加环境变量2.4. 检查是否安装好了 最近需要更新服务器的显卡驱动和C…

SpringBoot3使用响应Result类返回的响应状态码为406

Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: No acceptable representation] 解决方法&#xff1a;Result类上加上Data注解

文件包含一-WEB攻防-PHP应用文件包含LFIRFI伪协议编码算法无文件利用黑白盒

演示案例&#xff1a; 文件包含-原理&分类&利用&修复黑盒利用-VULWEB-有无包含文件白盒利用-CTFSHOW-伪协议玩法 #文件包含-原理&分类&利用&修复 1、原理 程序开发人员通常会把可重复使用的函数写到单个文件中&#xff0c;在使用某些函数时&#xff0c…

YOLOV5 改进:替换backbone为Swin Transformer

1、前言 本文会将YOLOV5 backbone更换成Swin Transformer 具体为什么这样实现参考上文:YOLOV5 改进:替换backbone(MobileNet为例)-CSDN博客 这里只贴加入的代码 训练结果如下: 2、common文件更改 在common文件中加入下面代码: 这里是swin transformer的实现,参考:…

React函数组件Hook

问题: 相对于类组件, 函数组件的编码更简单, 效率也更高, 但函数组件不能有state (旧版) 解决: React 16.8版本设计了一套新的语法来让函数组件也可以有state Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性 Hook也叫钩子…

软件测试 -- Selenium常用API全面解答(java)

写在前面 // 如果文章有问题的地方, 欢迎评论区或者私信指正 目录 什么是Selenium 一个简单的用例 元素定位 id定位 xpath定位 name定位 tag name 定位和class name 定位 操作元素 click send_keys submit text getAttribute 添加等待 显示等待 隐式等待 显示等…

【网络安全】CobaltStrike 使用

本文章仅用于信息安全学习&#xff0c;请遵守相关法律法规&#xff0c;严禁用于非法途径。若读者因此作出任何危害网络安全的行为&#xff0c;后果自负&#xff0c;与作者无关。 Cobalt Strike是一款渗透测试神器&#xff0c;Cobalt Strike已经不再使用MSF而是作为单独的平台使…

[NLP] 初窥000001

NL(natural language)–自然语言 人类的语言–中文&#xff0c;英语&#xff0c;法语 NLP(Natural Language Processing)–自认语言处理 计算机处理人类语言的技术&#xff0c;它包含翻译、智能问答、文本分类、情感分析等常见应用。 CV(Computational Vision) 感知NLP 认知…

Vue使用font-face自定义字体详解

目录 1 介绍2 使用2.1 语法2.2 属性说明2.3 Vue使用案例2.3.1 全局定义字体2.3.2 在页面使用 3 注意事项 1 介绍 font-face 是 CSS 中的一个规则&#xff0c;它允许你加载服务器上的字体文件&#xff08;远程或者本地&#xff09;&#xff0c;并在网页中使用这些字体。这样&am…

【C语言】——指针四:字符指针与函数指针变量

【C语言】——指针四&#xff1a;字符指针与函数指针变量 一、字符指针二、函数指针变量2.1、 函数指针变量的创建2.2、两段有趣的代码 三、typedef关键字3.1、typedef的使用3.2、typedef与define比较 四、函数指针数组 一、字符指针 在前面的学习中&#xff0c;我们知道有一种…

LeetCode Python - 71. 简化路径

目录 题目描述解法运行结果 题目描述 给你一个字符串 path &#xff0c;表示指向某一文件或目录的 Unix 风格 绝对路径 &#xff08;以 ‘/’ 开头&#xff09;&#xff0c;请你将其转化为更加简洁的规范路径。 在 Unix 风格的文件系统中&#xff0c;一个点&#xff08;.&…