在牛某网看见了牛肉哥的帖子之后,打算向牛肉大佬学习,故开始书写优快云博客,通过博客的方式来巩固自身知识学习。
因为之前有粗略的学习了Java Web的基础课程,所以博客内容主要是巩固之前学习当中的模糊点,以及一些自己认为重要的内容,用于自己进一步的掌握开发技能。
目录
完善登录功能:
在苍穹外卖的第一天,完善了登录功能,将密码加密存入数据库,提高安全性。
对于登录验证功能,就要知道JWT(JSON Web Token)
JWT:
JWT将原始的JSON格式数据,转变为字符串,利用一次base64编码
JWT就是将原始的json数据格式进行了安全的封装,这样就可以直接基于jwt在通信双方安全的进行信息传输。
JWT的组成:
(JWT令牌由三个部分组成,三个部分之间使用英文的点来分割)
-
第一部分:Header(头), 记录令牌类型、签名算法等。
-
第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。
-
第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload,并加入指定秘钥,通过指定签名算法计算而来。
Controller层:
首先来看Controller层,
- 首先是调用login函数去查询数据库,返回到employee,包含一些用户信息,详看该数据表
- 创建集合claims,存储键值对
- 新增键值对:empid--getid()
- 生成JWT令牌:它将一个保密的密钥(用于签名)、一个有效期(用于控制 Token 时效性)、一组自定义的用户信息(Claims,用于在服务间传递身份和权限)结合起来,生成了一个唯一的、可验证的、有期限的 JWT 字符串。
- 返回一个employeeLoginVO
/**
* 登录
*
* @param employeeLoginDTO
* @return
*/
@PostMapping("/login")
public Result<EmployeeLoginVO> login(@RequestBody EmployeeLoginDTO employeeLoginDTO) {
log.info("员工登录:{}", employeeLoginDTO);
//调用service方法查询数据库
Employee employee = employeeService.login(employeeLoginDTO);
//登录成功后,生成jwt令牌
Map<String, Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.EMP_ID, employee.getId());
String token = JwtUtil.createJWT(
jwtProperties.getAdminSecretKey(),
jwtProperties.getAdminTtl(),
claims);
EmployeeLoginVO employeeLoginVO = EmployeeLoginVO.builder()
.id(employee.getId())
.userName(employee.getUsername())
.name(employee.getName())
.token(token)
.build();
return Result.success(employeeLoginVO);
}
补充:
细看JwtUtil类:
创建JWT:需要自定义的claims信息,需要签名算法与签名秘钥。设置过期时间
package com.sky.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;
public class JwtUtil {
/**
* 生成jwt
* 使用Hs256算法, 私匙使用固定秘钥
*
* @param secretKey jwt秘钥
* @param ttlMillis jwt过期时间(毫秒)
* @param claims 设置的信息
* @return
*/
public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {
// 指定签名的时候使用的签名算法,也就是header那部分
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 生成JWT的时间
long expMillis = System.currentTimeMillis() + ttlMillis;
Date exp = new Date(expMillis);
// 设置jwt的body
JwtBuilder builder = Jwts.builder()
// 如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
.setClaims(claims)
// 设置签名使用的签名算法和签名使用的秘钥
.signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
// 设置过期时间
.setExpiration(exp);
return builder.compact();
}
/**
* Token解密
*
* @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个
* @param token 加密后的token
* @return
*/
public static Claims parseJWT(String secretKey, String token) {
// 得到DefaultJwtParser
Claims claims = Jwts.parser()
// 设置签名的秘钥
.setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
// 设置需要解析的jwt
.parseClaimsJws(token).getBody();
return claims;
}
}
Service层:
再来看Service的login方法:
- 先获取用户在浏览器中输入的用户名与密码
- 调用mapper层的getbyusername函数,获取数据库中的相应信息
- 比对信息,返回不同情况的信息
/**
* 员工登录
*
* @param employeeLoginDTO
* @return
*/
public Employee login(EmployeeLoginDTO employeeLoginDTO) {
String username = employeeLoginDTO.getUsername();
String password = employeeLoginDTO.getPassword();
//1、根据用户名查询数据库中的数据
Employee employee = employeeMapper.getByUsername(username);
//2、处理各种异常情况(用户名不存在、密码不对、账号被锁定)
if (employee == null) {
//账号不存在
throw new AccountNotFoundException(MessageConstant.ACCOUNT_NOT_FOUND);
}
//密码比对
if (!password.equals(employee.getPassword())) {
//密码错误
throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);
}
if (employee.getStatus() == StatusConstant.DISABLE) {
//账号被锁定
throw new AccountLockedException(MessageConstant.ACCOUNT_LOCKED);
}
//3、返回实体对象
return employee;
}
Mapper层:
最后看mapper层的代码:
@Select("select * from employee where username = #{username}"):返回查询到的对应一行
package com.sky.mapper;
import com.sky.entity.Employee;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface EmployeeMapper {
/**
* 根据用户名查询员工
* @param username
* @return
*/
@Select("select * from employee where username = #{username}")
Employee getByUsername(String username);
}
注:
Builder():
它为你提供了一个清晰、灵活、易读的方式来一步步配置和构建一个完整的 JWT 令牌。你通过链式调用设置 JWT 的各个部分,最后调用 .compact() 方法完成构建并获取最终的JWT 字符串。
@RequestBody:
将HTTP请求体中的原始数据(如JSON字符串)自动转换为Java对象,无需手动解析。
不适用于单字段参数:若仅需接收单个字段,应使用:RequestParam
Swagger的使用:
Swagger:
是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务
knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,
使用步骤:
引入依赖:
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
</dependency>
配置knife4j:
@Bean
public Docket docket() {
ApiInfo apiInfo = new ApiInfoBuilder()
.title("苍穹外卖项目接口文档")
.version("2.0")
.description("苍穹外卖项目接口文档")
.build();
//创建一个 ApiInfo 对象,该对象包含了 API 文档的元信息,会显示在生成的文档网页的顶部。
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo)
.select()
.apis(RequestHandlerSelectors.basePackage("com.sky.controller"))
//指定了 Springfox 要扫描哪个包下的 @Controller 类。
.paths(PathSelectors.any())
.build();
return docket;
}
当你启动 Spring Boot 应用时,Spring 会发现这个 docket() 方法,执行它并获取 Docket 实例。Springfox-Swagger 会根据 Docket 中的配置,自动扫描 com.sky.controller 包下的所有 Controller,分析其中的接口和注解(包括参数、返回值、注释等),然后生成一个符合 Swagger 2.0 规范的 JSON 文档。
同时,它还会提供一个默认的 UI 界面(通常在 /swagger-ui.html 或 /swagger-ui/ 路径下),你可以通过浏览器访问这个地址,看到一个交互式的 API 文档网页。在这个网页上,你可以:
- 浏览所有已定义的 API 接口。
- 查看每个接口的详细信息,包括 HTTP 方法、URL、请求参数、响应状态码等。
- 直接在网页上点击 “Try it out” 来发送 API 请求,测试接口功能。
设置静态资源映射:
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/doc.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
这段代码的核心作用是配置 Spring MVC 的静态资源处理器,它允许浏览器通过 URL 直接访问Swagger UI,目的是能够通过浏览器访问 API 文档的 Web 界面。
当客户端(浏览器)请求 /doc.html 这个 URL 时,去 classpath(类路径)下的 /META-INF/resources/ 目录中查找名为 doc.html 的文件,并把它返回给浏览器
浏览器在加载 doc.html 页面后,页面中的 JavaScript 和 CSS 文件可能会请求类似 /webjars/swagger-ui/4.15.5/swagger-ui-bundle.js 这样的 URL。这个请求会匹配到 /webjars/** 规则。
访问测试:
接口文档访问路径为 http://ip:port/doc.html
常用注解
通过注解可以控制生成的接口文档,常用注解如下:
| 注解 | 说明 |
|---|---|
| @Api | 用在类上,例如Controller,表示对类的说明,tag=" " |
| @ApiModel | 用在类上,例如entity、DTO、VO description = " " |
| @ApiModelProperty | 用在属性上,描述属性信息 |
| @ApiOperation | 用在方法上,例如Controller的方法,说明方法的用途、作用 value=" " |
这些注解是用于方便显示与理解的,这里不做过多解释,需要知道各注解所用位置。
下面是对比图:
未用:
已用:


1882

被折叠的 条评论
为什么被折叠?



