Day02
1.通用异常
1.1.什么是通用异常
目前的代码中如果发生系统异常,则直接会给用户抛出不友好的异常信息。为了增加用户的体验,应该给一些适当信息进行提示。例如删除频道的代码,如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6HiIZubi-1626096792817)(assets/1603460329508.png)]
目前红框圈起来的是可以给用户友好提示的,但是当执行删除这一行代码,如何失败了该如何处理。有可能系统会抛出异常。那这个时候就不应该把异常信息直接返回给用户。那该如何处理呢?
项目开发中肯定会设置全局异常处理,不管系统发生了任何不可知的异常信息,都应该给用户返回友好提示信息。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xd9jE9kF-1626096792820)(assets/1603460375653.png)]
通用异常的优势:
1.不用在每个controller中的方法中进行异常的处理了,可以统一抛给全局异常处理器进行处理。
2.不会将异常的具体信息展示给用户,而是在全局异常处理器中进行优化,展示给用户友好提示。
1.2.通用异常配置
1.在heima-leadnews-common模块中新建类ExceptionCatch
@ControllerAdvice//控制器增强
public class ExceptionCatch {
//拦截指定异常信息,捕获Exception此类异常
//当出现Exception异常的时候,就会执行以下此方法,异常信息会存放到方法参数中
@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseResult exception(Exception exception) {
exception.printStackTrace();
//返回通用异常
return ResponseResult.errorResult(AppHttpCodeEnum.SERVER_ERROR);
}
}
2.在heima-leadnews-admin模块中新增类ExceptionCatchConfig
@Configuration
@ComponentScan("com.heima.common.exception")
public class ExceptionCatchConfig {
}
1.3.通用异常测试
1.在heima-leadnews-admin模块中的AdChannelServiceImpl类的deleteById方法中增加int i = 10/0;
@Override
public ResponseResult deleteById(Integer id) {
//1.检查参数
if(id == null){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//2.判断当前频道是否存在 和 是否有效
AdChannel adChannel = getById(id);
if(adChannel==null){
return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
}
if(adChannel.getStatus()){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID,"频道有效不能删除");
}
int i = 10/0;
//3.删除频道
removeById(id);
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
2.启动项目测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jNug73GC-1626096792822)(assets/1603461847027.png)]
2.前端相关基础知识(了解)
2.1.webpack
2.1.1.什么是webpack
webpack 是一个现代 JavaScript 应用程序的模块打包器(module bundler),分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Sass,TypeScript等),并将其转换和打包为合适的格式供浏览器使用。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-11sqvCcm-1626096792824)(assets/1603462129619.png)]
2.1.2.webpack安装
1.node.js安装
webpack基于node.js运行,首先需要安装node.js。(必须安装)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SS7jziIr-1626096792826)(assets/1603462371902.png)]
安装完node.js,通过cmd命令提示窗口输入相关指令,如果提示以下信息就表示node.js安装成功。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IiDISo5N-1626096792827)(assets/1603462888245.png)]
2.webpack安装
执行以下指令安装webpack
npm install webpack@3.6.0 -g
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7RQwInI0-1626096792829)(assets/1603463049304.png)]
安装webpack之前,建议大家这样操作(随时切换镜像源):
- npm install nrm -g // 安装nrm
- nrm ls // 查看镜像源
- nrm use taobao // 选择淘宝镜像,也可以选择cnpm
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YiurY9PI-1626096792830)(assets/1603463378322.png)]
3.vue脚手架(可不做)
vue-cli是官方的一个脚手架工具,所谓脚手架呢就是一个架子,什么架子呢?项目结构的架子,里面有一些最基本的结构配置。利用vue-cli呢,我们可以生成这样的一个脚手架,所以呢它就被称为vue脚手架工具。
安装
npm install vue-cli -g
初始化项目
vue init webpack admin
2.2.admin前台系统搭建
1.安装webstorm(跟IDEA安装操作相似)
webstorm和IDEA是一家公司的产品,所以激活方式是一致的(IDEA的激活方式同样适用于webstorm)。
2.使用webstorm工具打开资料中前端项目
3.安装js依赖,保证有网络,在项目的根目录执行命令npm install
如果webstorm的命令提示窗口不可用,可以进行如下操作调试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HQDNB2wM-1626096792831)(assets/1603464610662.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0P66jeqE-1626096792833)(assets/1603464629299.png)]
之后重新启动系统,即可在webstorm内的命令提示窗口进行操作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pAdv9e9Y-1626096792834)(assets/1603464735681.png)]
执行完之后,会在项目的根目录中看node_modules目录。
可以在config的index.js文件中看到前端项目的服务器配置信息。
4.修改文件index.vue和index.js,暂时不登录
修改index.vue效果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4TVMAd5q-1626096792835)(assets/1603465013630.png)]
修改index.js效果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hrh7VdOm-1626096792835)(assets/1603465881452.png)]
5.启动项目
在package.json文件上右键,选择Show npm Scripts
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DqujrcQP-1626096792836)(assets/1603465074594.png)]
在弹出的窗口中双击dev
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wHqjxFN2-1626096792837)(assets/1603465141139.png)]
6.启动成功以后,用浏览器打开,可以测试已开发好的频道功能
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N3aBNbwt-1626096792838)(assets/1603465906569.png)]
可以在此页面进行模糊查询测试、新建测试、启动、禁用测试、删除测试。
7.修改后端heima-leadnews-model中的ResponseResult中的code=0(原来code=200)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TCFKmjbF-1626096792839)(assets/1608795198514.png)]
3.平台运营端
3.1.敏感词管理
3.1.1.需求分析及接口定义
需求分析
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eU6Tz4YC-1626096792840)(assets/1603540108081.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8ea70MAW-1626096792841)(assets/1603540128277.png)]
leadnews_admin管理平台(内部工作人员使用)数据库表:ad_sensitive 敏感词表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NInvlT9h-1626096792842)(assets/1603540161835.png)]
操作步骤
1.在heima-leadnews-model中创建实体类AdSensitive
/**
* <p>
* 敏感词信息表
* </p>
*
* @author itheima
*/
@Data
@TableName("ad_sensitive")
public class AdSensitive implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
/**
* 敏感词
*/
@TableField("sensitives")
private String sensitives;
/**
* 创建时间
*/
@TableField("created_time")
private Date createdTime;
}
2.在heima-leadnews-apis中创建AdSensitiveControllerApi接口
public interface AdSensitiveControllerApi {
/**
* 根据名称分页查询敏感词
* @param dto
* @return
*/
public ResponseResult list(SensitiveDto dto);
/**
* 新增
* @param adSensitive
* @return
*/
public ResponseResult save(AdSensitive adSensitive);
/**
* 修改
* @param adSensitive
* @return
*/
public ResponseResult update(AdSensitive adSensitive);
/**
* 删除敏感词
* @param id
* @return
*/
public ResponseResult deleteById(Integer id);
}
在heima-leadnews-model中创建SensitiveDto
@Data
public class SensitiveDto extends PageRequestDto {
/**
* 敏感词名称
*/
private String name;
}
3.1.2.列表查询
1.在heima-leadnews-admin中创建AdSensitiveMapper
@Mapper
public interface AdSensitiveMapper extends BaseMapper<AdSensitive> {
}
2.在heima-leadnews-admin中创建AdSensitiveService和AdSensitiveServiceImpl
public interface AdSensitiveService extends IService<AdSensitive> {
/**
* 根据名称分页查询敏感词
* @param dto
* @return
*/
public ResponseResult list(SensitiveDto dto);
/**
* 新增
* @param adSensitive
* @return
*/
public ResponseResult insert(AdSensitive adSensitive);
/**
* 修改
* @param adSensitive
* @return
*/
public ResponseResult update(AdSensitive adSensitive);
/**
* 删除敏感词
* @param id
* @return
*/
public ResponseResult deleteById(Integer id);
}
@Service
public class AdSensitiveServiceImpl extends ServiceImpl<AdSensitiveMapper, AdSensitive> implements AdSensitiveService {
@Override
public ResponseResult list(SensitiveDto dto) {
//1.检查参数
if(dto==null){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
dto.checkParam();
//2.根据名称模糊分页查询
Page page = new Page<>(dto.getPage(),dto.getSize());
LambdaQueryWrapper<AdSensitive> lambdaQueryWrapper = new LambdaQueryWrapper();
if(StringUtils.isNotBlank(dto.getName())){
lambdaQueryWrapper.like(AdSensitive::getSensitives,dto.getName());
}
IPage result = page(page, lambdaQueryWrapper);
//3.结果返回
ResponseResult responseResult = new PageResponseResult(dto.getPage(),dto.getSize(),(int)result.getTotal());
responseResult.setData(result.getRecords());
return responseResult;
}
@Override
public ResponseResult insert(AdSensitive adSensitive) {
return null;
}
@Override
public ResponseResult update(AdSensitive adSensitive) {
return null;
}
@Override
public ResponseResult deleteById(Integer id) {
return null;
}
}
3.在heima-leadnews-admin中创建AdSensitiveController
@RestController
@RequestMapping("/api/v1/sensitive")
public class AdSensitiveController implements AdSensitiveControllerApi {
@Autowired
private AdSensitiveService adSensitiveService;
@PostMapping("/list")
@Override
public ResponseResult list(@RequestBody SensitiveDto dto) {
return adSensitiveService.list(dto);
}
@Override
public ResponseResult save(@RequestBody AdSensitive adSensitive) {
return null;
}
@Override
public ResponseResult update(@RequestBody AdSensitive adSensitive) {
return null;
}
@Override
public ResponseResult deleteById(@PathVariable("id") Integer id) {
return null;
}
}
3.1.3.新增修改删除
1.在heima-leadnews-admin中的AdSensitiveServiceImpl完成新增、修改、删除操作
@Service
public class AdSensitiveServiceImpl extends ServiceImpl<AdSensitiveMapper, AdSensitive> implements AdSensitiveService {
/**
* 列表查询
* @param dto
* @return
*/
@Override
public ResponseResult list(SensitiveDto dto) {
//1.检查参数
if(dto==null){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
dto.checkParam();
//2.根据名称模糊分页查询
Page page = new Page<>(dto.getPage(),dto.getSize());
LambdaQueryWrapper<AdSensitive> lambdaQueryWrapper = new LambdaQueryWrapper();
if(StringUtils.isNotBlank(dto.getName())){
lambdaQueryWrapper.like(AdSensitive::getSensitives,dto.getName());
}
IPage result = page(page, lambdaQueryWrapper);
//3.结果返回
ResponseResult responseResult = new PageResponseResult(dto.getPage(),dto.getSize(),(int)result.getTotal());
responseResult.setData(result.getRecords());
return responseResult;
}
/**
* 新增
* @param adSensitive
* @return
*/
@Override
public ResponseResult insert(AdSensitive adSensitive) {
//1.参数检查
if(adSensitive == null){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//2.保存
adSensitive.setCreatedTime(new Date());
save(adSensitive);
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
/**
* 更新
* @param adSensitive
* @return
*/
@Override
public ResponseResult update(AdSensitive adSensitive) {
//1.参数检查
if(adSensitive == null || adSensitive.getId() == null){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//2.修改
updateById(adSensitive);
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
/**
* 删除
* @param id
* @return
*/
@Override
public ResponseResult deleteById(Integer id) {
//1.参数检查
if(id == null){
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID);
}
//2.判断当前敏感词是否存在
AdSensitive adSensitive = getById(id);
if(adSensitive == null){
return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
}
//3.删除操作
removeById(id);
return ResponseResult.okResult(AppHttpCodeEnum.SUCCESS);
}
}
2.在heima-leadnews-admin中的AdSensitiveController完成新增、修改、删除操作
@RestController
@RequestMapping("/api/v1/sensitive")
public class AdSensitiveController implements AdSensitiveControllerApi {
@Autowired
private AdSensitiveService adSensitiveService;
/**
* 列表查询
* @param dto
* @return
*/
@PostMapping("/list")
@Override
public ResponseResult list(@RequestBody SensitiveDto dto) {
return adSensitiveService.list(dto);
}
/**
* 新增
* @param adSensitive
* @return
*/
@PostMapping("/save")
@Override
public ResponseResult save(@RequestBody AdSensitive adSensitive) {
return adSensitiveService.insert(adSensitive);
}
/**
* 更新
* @param adSensitive
* @return
*/
@PostMapping("/update")
@Override
public ResponseResult update(@RequestBody AdSensitive adSensitive) {
return adSensitiveService.update(adSensitive);
}
/**
* 删除
* @param id
* @return
*/
@DeleteMapping("/del/{id}")
@Override
public ResponseResult deleteById(@PathVariable("id") Integer id) {
return adSensitiveService.deleteById(id);
}
}
3.1.4.敏感词功能与前台系统综合测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WZkhZKqw-1626096792843)(assets/1603541913447.png)]
4.密码加密方式
内部工作人员使用的平台登录的操作:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PhHZGQ14-1626096792844)(assets/1608798371991.png)]
4.1.可逆加密算法
**应用场景:**主要针对数据传输,流入传输Token处理
**特征:**加密后, 密文可以反向解密得到密码原文(明文)。
4.1.1.对称加密
文件加密和解密使用相同的密钥,即加密密钥也可以用作解密密钥。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RYIbLIZC-1626096792844)(assets/1603542194160.png)]
解释: 在对称加密算法中,数据发信方将明文和加密密钥一起经过特殊的加密算法处理后,使其变成复杂的加密密文发送出去,收信方收到密文后,若想解读出原文,则需要使用加密时用的密钥以及相同加密算法的逆算法对密文进行解密,才能使其回复成可读明文。在对称加密算法中,使用的密钥只有一个,收发双方都使用这个密钥,这就需要解密方事先知道加密密钥。
优点: 对称加密算法的优点是算法公开、计算量小、加密速度快、加密效率高。
缺点: 没有非对称加密安全。
用途: 一般用于保存用户手机号、身份证等敏感但能解密的信息。
常见的对称加密算法有: AES、DES、3DES、Blowfish、IDEA、RC4、RC5、RC6、HS256
4.1.2.非对称加密
两个密钥:公开密钥(publickey)和私有密钥,公有密钥加密,私有密钥解密。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MbHOKmCJ-1626096792848)(assets/1603542327963.png)]
**解释: ** 同时生成两把密钥:私钥和公钥,私钥隐秘保存,公钥可以下发给信任客户端.
加密与解密:
- 私钥加密,持有公钥才可以解密
- 公钥加密,持有私钥才可解密
签名:
- 私钥签名, 持有公钥进行验证是否被篡改过.
**优点: ** 非对称加密与对称加密相比,其安全性更好;
缺点: 非对称加密的缺点是加密和解密花费时间长、速度慢,只适合对少量数据进行加密。
用途: 一般用于签名和认证。私钥服务器保存, 用来加密, 公钥客户拿着用于对于令牌或者签名的解密或者校验使用。
常见的非对称加密算法有: RSA、DSA(数字签名用)、ECC(移动设备用)、RS256 (采用SHA-256 的 RSA 签名)
**应用场景:**https
4.2.不可逆加密算法
**应用场景:**在注册或登录的时候进行密码加密使用,登录的时候是拿着密文进行比较的,一致:登录,不一致:不能登陆。
解释: 一旦加密就不能反向解密得到密码原文。
种类: Hash加密算法, 散列算法, 摘要算法等。
**用途:**一般用于效验下载文件正确性,一般在网站上下载文件都能见到;存储用户敏感信息,如密码、 卡号等不可解密的信息。
常见的不可逆加密算法有: MD5、SHA、HMAC
4.3.Base64编码
Base64是网络上最常见的用于传输8Bit字节代码的编码方式之一。Base64编码可用于在HTTP环境下传递较长的标识信息。采用Base64Base64编码解码具有不可读性,即所编码的数据不会被人用肉眼所直接看到。注意:Base64只是一种编码方式,不算加密方法。
在线编码工具:
http://www.jsons.cn/img2base64/
4.4.密码加密及密码加密方式选型
主要是以不可逆加密为主,适用于登陆页面输入登陆密码,判断密码是否正确
4.2.1.MD5密码加密
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8GlsOH9X-1626096792849)(assets/1603542672507.png)]
示例代码
在heima-leadnews-common中创建MD5Test
public class MD5Test {
public static void main(String[] args) {
//md5加密 DegestUtils:spring框架提供的工具类
String md5Str = DigestUtils.md5DigestAsHex("abc".getBytes());
System.out.println(md5Str);//900150983cd24fb0d6963f7d28e17f72
}
}
md5相同的密码每次加密都一样,不太安全。
4.2.2.手动加密(md5+随机字符串)
在md5的基础上手动加盐(salt)处理。
示例代码
在heima-leadnews-common中创建MD5Test
public class MD5Test {
public static void main(String[] args) {
//uername:zhangsan password:123 salt:随机字符串
String salt = RandomStringUtils.randomAlphanumeric(10);
System.out.println(salt);
String pswd = "123"+salt;
String saltPswd = DigestUtils.md5DigestAsHex(pswd.getBytes());
System.out.println(saltPswd);
}
}
这样同样的密码,加密多次值是不相同的,因为加入了随机字符串。
实际场景中的做法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ajJqbMFu-1626096792850)(assets/1603543196239.png)]
4.2.3.BCrypt密码加密
在用户模块,对于用户密码的保护,通常都会进行加密。我们通常对密码进行加密,然后存放在数据库中,在用户进行登录的时候,将其输入的密码进行加密然后与数据库中存放的密文进行比较,以验证用户密码是否正确。 目前,MD5和BCrypt比较流行。相对来说,BCrypt比MD5更安全。
BCrypt 官网http://www.mindrot.org/projects/jBCrypt/
- 我们从官网下载源码
- 新建工程,将源码类BCrypt拷贝到工程
- 新建测试类,main方法中编写代码,实现对密码的加密
示例代码
在heima-leadnews-common中创建MD5Test
public class MD5Test {
public static void main(String[] args) {
String gensalt = BCrypt.gensalt();//这个是盐 29个字符,随机生成
System.out.println(gensalt);
String password = BCrypt.hashpw("123456", gensalt); //根据盐对密码进行加密
System.out.println(password);//加密后的字符串前29位就是盐
//校验密码
boolean abc = BCrypt.checkpw("123456", password);
System.out.println(abc);
}
}
4.3.jwt介绍
适用于判断操作某个功能的时候是否登陆过。
4.3.1.token认证流程介绍
随着 Restful API、微服务的兴起,基于 Token 的认证现在已经越来越普遍。基于token的用户认证是一种服务端无状态的认证方式,所谓服务端无状态指的token本身包含登录用户所有的相关数据,而客户端在认证后的每次请求都会携带token,因此服务器端无需存放token数据。
当用户认证后,服务端生成一个token发给客户端,客户端可以放到 cookie 或 localStorage 等存储中,每次请求时带上 token,服务端收到token通过验证后即可确认用户身份。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8FHBdp2x-1626096792850)(assets/1603544111255.png)]
4.3.2.JWT
我们现在了解了基于token认证的交互机制,但令牌里面究竟是什么内容?什么格式呢?市面上基于token的认证方式大都采用的是JWT(Json Web Token)。
JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简洁的、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。
JWT令牌结构:
JWT令牌由Header、Payload、Signature三部分组成,每部分中间使用点(.)分隔,比如:xxxxx.yyyyy.zzzzz
比如:eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAADWLQQqEMAwA_5KzhURNt_qb1KZYQSi0wi6Lf9942NsMw3zh6AVW2DYmDGl2WabkZgreCaM6VXzhFBfJMcMARTqsxIG9Z888QLui3e3Tup5Pb81013KKmVzJTGo11nf9n8v4nMUaEY73DzTabjmDAAAA.4SuqQ42IGqCgBai6qd4RaVpVxTlZIWC826QA9kLvt9d-yVUw82gU47HDaSfOzgAcloZedYNNpUcd18Ne8vvjQA
- Header
头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC、SHA256或RSA)。
一个例子:
{
"alg": "HS256",
"typ": "JWT"
}
将上边的内容使用Base64Url编码,得到一个字符串就是JWT令牌的第一部分。
- Payload
第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比
如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。
此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。
一个例子:
{
"sub": "1234567890",
"name": "abc",
"admin": true
}
最后将第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。
- Signature
第三部分是签名,此部分用于防止jwt内容被篡改。
这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明
签名算法进行签名。
一个例子:
HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)
base64UrlEncode(header):jwt令牌的第一部分。
base64UrlEncode(payload):jwt令牌的第二部分。
secret:签名所使用的密钥。
下图中包含一个生成的jwt令牌:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CiCpJzVH-1626096792851)(assets/1603544403482.png)]
4.3.3.JWT工具类
1.在heima-leadnews-utils的pom.xml文件中引入依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
2.在heima-leadnews-utils的AppJwtUtil.java中编写生成JWT
package com.heima.utils.common;
import io.jsonwebtoken.*;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.*;
public class AppJwtUtil {
// TOKEN的有效期一天(S) 1个小时
private static final int TOKEN_TIME_OUT = 3600;
// 加密KEY
private static final String TOKEN_ENCRY_KEY = "MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY";
// 最小刷新间隔(S)
private static final int REFRESH_TIME = 300;
/**
* 由字符串生成加密key
*
* @return
*/
public static SecretKey generalKey() {
byte[] encodedKey = Base64.getEncoder().encode(TOKEN_ENCRY_KEY.getBytes());
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
/**
* 生成token,token包含id
* @param id
* @return
*/
// 生产ID
public static String getToken(Long id){//id:往token第二部分存储的自定义数据(登录用户的ID)
Map<String, Object> claimMaps = new HashMap<>();
claimMaps.put("id",id);
long currentTime = System.currentTimeMillis();
return Jwts.builder()
.setId(UUID.randomUUID().toString())
.setIssuedAt(new Date(currentTime)) //签发时间
.setSubject("system") //说明
.setIssuer("heima") //签发者信息
.setAudience("app") //接收用户
.compressWith(CompressionCodecs.GZIP) //数据压缩方式
.signWith(SignatureAlgorithm.HS512, generalKey()) //加密方式
.setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000)) //过期时间戳
.addClaims(claimMaps) //cla信息,设置token第二部分payload存储的数据
.compact();
}
/**
* 获取hearder header信息
*
* @param token
* @return
*/
public static JwsHeader getHeaderBody(String token) {
return getJws(token).getHeader();
}
/**
* 解析token
* 获取token中的claims信息对象
*
* @param token
* @return
*/
private static Jws<Claims> getJws(String token) {
return Jwts.parser()
.setSigningKey(generalKey())
.parseClaimsJws(token);
}
/**
* 获取payload payload信息
*
* @param token
* @return
*/
public static Claims getClaimsBody(String token) {
try {
return getJws(token).getBody();
}catch (ExpiredJwtException e){
return null;
}
}
/**
* 是否过期
*
* @param claims
* @return -1:有效,0:过期,1:异常,2:异常
*/
public static int verifyToken(Claims claims) {
if(claims==null){
return 1;
}
try {
/*
boolean before = claims.getExpiration()
.before(new Date());
if (before){
return -1;
}else{
return 0;
}
*/
// token到期时间 - 当前时间 > 300(秒) * 1000 300000毫秒 5分钟
if((claims.getExpiration().getTime()-System.currentTimeMillis())>REFRESH_TIME*1000){
return -1;//有效
}else {
return 0;//过期
}
} catch (ExpiredJwtException ex) {
return 1;
}catch (Exception e){
return 2;
}
}
/**
* 刷新令牌
*
* @param token 原令牌
* @return 新令牌
*/
public String refreshToken(String token) {
String refreshedToken;
try {
Claims claims = getClaimsBody(token);
claims.put("created", new Date());
Date expirationDate = new Date(System.currentTimeMillis() + TOKEN_TIME_OUT);
refreshedToken = Jwts.builder().setClaims(claims)
.setExpiration(expirationDate)
.signWith(SignatureAlgorithm.HS512, generalKey())
.compact();
} catch (Exception e) {
refreshedToken = null;
}
return refreshedToken;
}
public static void main(String[] args) {
/* Map map = new HashMap();
map.put("id","11");*/
System.out.println(AppJwtUtil.getToken(1102L));
System.out.println(AppJwtUtil.getToken(1L));
Jws<Claims> jws = AppJwtUtil.getJws("eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAADWLQQqEMAwA_5KzhURNt_qb1KZYQSi0wi6Lf9942NsMw3zh6AVW2DYmDGl2WabkZgreCaM6VXzhFBfJMcMARTqsxIG9Z888QLui3e3Tup5Pb81013KKmVzJTGo11nf9n8v4nMUaEY73DzTabjmDAAAA.4SuqQ42IGqCgBai6qd4RaVpVxTlZIWC826QA9kLvt9d-yVUw82gU47HDaSfOzgAcloZedYNNpUcd18Ne8vvjQA");
Claims claims = jws.getBody();//获取中间部分的信息 Payload
System.out.println(claims.get("id"));
}
}
5.admin端-登录实现
5.1.admin端登录功能思路分析和接口定义
leadnews_admin管理平台(内部工作人员使用)数据库表:ad_user 运营平台用户信息表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HwQn7Gqj-1626096792852)(assets/1603545255453.png)]
1.在heima-leadnews-model中创建AdUser
/**
* <p>
* 管理员用户信息表
* </p>
*
* @author itheima
*/
@Data
@TableName("ad_user")
public class AdUser implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 主键
*/
@TableId(value = "id",type = IdType.AUTO)
private Integer id;
/**
* 登录用户名
*/
@TableField("name")
private String name;
/**
* 登录密码
*/
@TableField("password")
private String password;
/**
* 盐
*/
@TableField("salt")
private String salt;
/**
* 昵称
*/
@TableField("nickname")
private String nickname;
/**
* 头像
*/
@TableField("image")
private String image;
/**
* 手机号
*/
@TableField("phone")
private String phone;
/**
* 状态
0 暂时不可用
1 永久不可用
9 正常可用
*/
@TableField("status")
private Integer status;
/**
* 邮箱
*/
@TableField("email")
private String email;
/**
* 最后一次登录时间
*/
@TableField("login_time")
private Date loginTime;
/**
* 创建时间
*/
@TableField("created_time")
private Date createdTime;
}
修改数据库leadnews_admin中的ad_user表,设置id自增
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iQ5Akqmz-1626096792853)(assets/1603545457325.png)]
2.在heima-leadnews-apis中创建LoginControllerApi
public interface LoginControllerApi {
/**
* admin登录功能
* @param dto
* @return
*/
public ResponseResult login(@RequestBody AdUserDto dto);
}
3.在heima-leadnews-model中创建AdUserDto
@Data
public class AdUserDto {
//用户名
private String name;
//密码
private String password;
}
5.2.admin端登录功能实现
1.在heima-leadnews-admin中创建AdUserMapper
@Mapper
public interface AdUserMapper extends BaseMapper<AdUser> {
}
2.在heima-leadnews-admin中创建UserLoginService
public interface UserLoginService extends IService<AdUser> {
/**
* 登录功能
* @param dto
* @return
*/
ResponseResult login(AdUserDto dto);
}
3.在heima-leadnews-admin中创建UserLoginServiceImpl
@Service
@Transactional
public class UserLoginServiceImpl extends ServiceImpl<AdUserMapper, AdUser> implements UserLoginService {
@Override
public ResponseResult login(AdUserDto dto) {
//1.参数校验 判断页面传递过来的用户名和密码是否为空 (null | "")
if (StringUtils.isEmpty(dto.getName()) || StringUtils.isEmpty(dto.getPassword())) {
//用户名或密码为空,返回给页面错误信息
return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_REQUIRE, "用户名或密码不能为空");
}
//用户名或者密码不为空,根据页面传递用户名查询数据库,虽然返回的是list,但是list中只有一个数据
List<AdUser> list = list(Wrappers.<AdUser>lambdaQuery().eq(AdUser::getName,dto.getName()));
//也可以使用以下写法
/*
LambdaQueryWrapper<AdUser> objectLambdaQueryWrapper = Wrappers.lambdaQuery();
objectLambdaQueryWrapper.eq(AdUser::getName,dto.getName());
List<AdUser> list = list(objectLambdaQueryWrapper);
*/
if (list != null && list.size() == 1) {//查询出用户信息操作
//获取查询出来的用户信息(密码的密文,盐)
AdUser adUser = list.get(0);
//用户已经查询出来了,接下来就是要判断密码是否正确了
//拿着页面输入的密码 + 盐 进行md5加密
String pswd = DigestUtils.md5DigestAsHex((dto.getPassword() + adUser.getSalt()).getBytes());
//之后和数据库查询出来的密码的密文进行比较
if (adUser.getPassword().equals(pswd)) { //密码一致
Map<String, Object> map = Maps.newHashMap();
adUser.setPassword("");
adUser.setSalt("");
map.put("token", AppJwtUtil.getToken(adUser.getId().longValue()));//登陆成功,生成token
map.put("user", adUser);
return ResponseResult.okResult(map);//登陆成功之后,将生成的token和用户的基本信息(不包含密码和盐)交给页面存储
} else {//密码不一致
return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);
}
} else {//查询不出来用户信息的操作
return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST, "用户不存在");
}
}
}
4.在heima-leadnews-admin中创建LoginController
@RestController
@RequestMapping("/login")
public class LoginController implements LoginControllerApi {
@Autowired
private UserLoginService userLoginService ;
@Override
@PostMapping("/in")
public ResponseResult login(@RequestBody AdUserDto dto){
return userLoginService.login(dto);
}
}
5.3.admin端登陆功能测试
1.给数据库leadnews_admin中的ad_user表增加guest用户的信息
salt:123456
password:34e20b52f5bd120db806e57e27f47ed0(guest+salt)
username:guest
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-28J2WoSF-1626096792854)(assets/1603547042224.png)]
2.启动项目,使用postman进行测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dYr7ySaK-1626096792869)(assets/1603547425807.png)]
扩展
可以修改前端项目的index.vue
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A6ym5Efz-1626096792869)(assets/1604802829832.png)]
直接通过页面测试登陆操作。
6.nacos介绍
6.1.简介
可以替代spring cloud eureka,作为注册中心进行使用。
Nacos是阿里的一个开源产品,它是针对微服务架构中的服务发现、配置管理、服务治理的综合型解决方案。
官方介绍是这样的:
Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您实现动态服务
发现、服务配置管理、服务及流量管理。 Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。
Nacos 是构建以“服务”为中心的现代应用架构的服务基础设施。
官网地址:https://nacos.io
官方文档:https://nacos.io/zh-cn/docs/what-is-nacos.html
Nacos主要提供以下四大功能:
- 服务发现与服务健康检查
Nacos使服务更容易注册,并通过DNS或HTTP接口发现其他服务,Nacos还提供服务的实时健康检查,以防
止向不健康的主机或服务实例发送请求。 - 动态配置管理
动态配置服务允许您在所有环境中以集中和动态的方式管理所有服务的配置。Nacos消除了在更新配置时重新
部署应用程序,这使配置的更改更加高效和灵活。 - 动态DNS服务
Nacos提供基于DNS 协议的服务发现能力,旨在支持异构语言的服务发现,支持将注册在Nacos上的服务以
域名的方式暴露端点,让三方应用方便的查阅及发现。 - 服务和元数据管理
Nacos 能让您从微服务平台建设的视角管理数据中心的所有服务及元数据,包括管理服务的描述、生命周
期、服务的静态依赖分析、服务的健康状态、服务的流量管理、路由及安全策略。
6.2.linux服务器挂载
1.打开当天资料文件中的镜像,拷贝到一个地方,然后解压
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oIMxMHOa-1626096792870)(assets/1603548799083.png)]
2.解压后,双击ContOS7-hmtt.vmx
文件,前提是电脑上已经安装了VMware
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hL28HrKA-1626096792871)(assets/1603548820166.png)]
3.修改虚拟网络地址(NAT)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z8D9mLLz-1626096792872)(assets/1603550294706.png)]
①,选中VMware中的编辑
②,选择虚拟网络编辑器
③,找到NAT网卡,把网段改为200(当前挂载的虚拟机已固定ip地址)
④,修改虚拟机的网络模式为NAT,如下图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GaJA8jdo-1626096792873)(assets/1603550315614.png)]
4.启动虚拟机,使用客户端工具连接虚拟机,用户名:root 密码:itcast
6.3.安装Nacos Server
在liunx下安装nacos必须先安装jdk8+才能运行
可以从https://github.com/alibaba/nacos/releases下载 nacos -server-$version.zip
包
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t4wACE1E-1626096792874)(assets/1603550546924.png)]
或者直接使用资料文件夹下已经提供好的安装包,上传到服务器上,上传服务器Alt+P
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-govpbJCl-1626096792876)(assets/1603550566163.png)]
下载后解压:
unzip nacos‐server‐$version.zip
或者
tar ‐xvf nacos‐server‐$version.tar.gz
进入安装程序的bin目录:
sh startup.sh -m standalone
如果您使用的是ubuntu系统,或者运行脚本报错提示[[符号找不到,可尝试如下运行:
bash startup.sh -m standalone
如果是Windows,启动命令:
cmd startup.cmd 或者双击 startup.cmd 运行文件
启动成功,可通过浏览器访问 http://192.168.200.130:8848/nacos ,打开如下nacos控制台登录页面:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U0tyQwgo-1626096792876)(assets/1603550680225.png)]
7.注册服务
1.在heima-leadnews-admin的pom.xml文件中引入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2.在heima-leadnews-admin的application.yml文件中配置
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.200.130:8848
3.在heima-leadnews-admin的的引导类中增加注解
@SpringBootApplication
@MapperScan("com.heima.admin.mapper")
@EnableDiscoveryClient
public class AdminApplication {
public static void main(String[] args) {
SpringApplication.run(AdminApplication.class,args);
}
/**
* mybatis-plus分页插件
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
4.启动项目,查看Nacos