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