前言
- 练习项目,参照https://blog.csdn.net/weixin_61061381/article/details/129096734
- 源码
- https://github.com/superBiuBiuMan/java-blog-system-practice
- 很多功能没做,毕竟是练习~
注意
- 项目为了练习,没有使用mybatisplus的简化写法
统一错误(异常)处理
-
还是这位博主的博客
- https://www.cnblogs.com/l-y-h/p
- 也学习这位博主的
- https://blog.csdn.net/weixin_46097842/article/details/118003715
-
使用统一结果处理时,有些异常我们可以提前预知并处理,但是一个运行时异常,我们不一定能预知并处理,这时可以使用统一异常处理,当异常发生时,触发该处理操作,从而保证程序的健壮性。
-
使用 @ControllerAdvice 或者 @RestControllerAdvice 注解作为统一异常处理的核心。
- 这两个注解都是 Spring MVC 提供的。作用于 控制层 的一种切面通知。
【@ControllerAdvice 与 @RestControllerAdvice 区别:】
@RestControllerAdvice 注解包含了 @ControllerAdvice 与 @ResponseBody 注解。
类似于 @Controller 与 @RestController 的区别。
@RestControllerAdvice 返回 json 数据时不需要添加 @ResponseBody 注解。
自定义一个异常类,用于处理项目中的异常,并收集异常信息。
package com.lyh.common.exception;
import lombok.Data;
import org.apache.http.HttpStatus;
/**
* 自定义异常,
* 可以自定义 异常信息 message 以及 响应状态码 code(默认为 500)。
*
* 依赖信息说明:
* 此处使用 @Data 注解,需导入 lombok 相关依赖文件。
* 使用 HttpStatus 的常量表示 响应状态码,需导入 httpcore 相关依赖文件。
*/
@Data
public class GlobalException extends RuntimeException {
/**
* 保存异常信息
*/
private String message;
/**
* 保存响应状态码
*/
private Integer code = HttpStatus.SC_INTERNAL_SERVER_ERROR;
/**
* 默认构造方法,根据异常信息 构建一个异常实例对象
* @param message 异常信息
*/
public GlobalException(String message) {
super(message);
this.message = message;
}
/**
* 根据异常信息、响应状态码构建 一个异常实例对象
* @param message 异常信息
* @param code 响应状态码
*/
public GlobalException(String message, Integer code) {
super(message);
this.message = message;
this.code = code;
}
/**
* 根据异常信息,异常对象构建 一个异常实例对象
* @param message 异常信息
* @param e 异常对象
*/
public GlobalException(String message, Throwable e) {
super(message, e);
this.message = message;
}
/**
* 根据异常信息,响应状态码,异常对象构建 一个异常实例对象
* @param message 异常信息
* @param code 响应状态码
* @param e 异常对象
*/
public GlobalException(String message, Integer code, Throwable e) {
super(message, e);
this.message = message;
this.code = code;
}
}
再定义一个全局的异常处理类 GlobalExceptionHandler。
- 使用 @RestControllerAdvice 注解标记这个类。
- 内部使用 @ExceptionHandler 注解去捕获异常。
package com.lyh.common.exception;
import com.lyh.common.util.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理类。
* 使用 slf4j 保存日志信息。
* 此处使用了 统一结果处理 类 Result 用于包装异常信息。
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
private Logger logger = LoggerFactory.getLogger(getClass());
/**
* 处理 Exception 异常
* @param e 异常
* @return 处理结果
*/
@ExceptionHandler(Exception.class)
public Result handlerException(Exception e) {
logger.error(e.getMessage(), e);
return Result.error().message("系统异常");
}
/**
* 处理空指针异常
* @param e 异常
* @return 处理结果
*/
@ExceptionHandler(NullPointerException.class)
public Result handlerNullPointerException(NullPointerException e) {
logger.error(e.getMessage(), e);
return Result.error().message("空指针异常");
}
/**
* 处理自定义异常
* @param e 异常
* @return 处理结果
*/
@ExceptionHandler(GlobalException.class)
public Result handlerGlobalException(GlobalException e) {
logger.error(e.getMessage(), e);
return Result.error().message(e.getMessage()).code(e.getCode());
}
}
使用
使用?
修改某个 controller 如下所示:
参数不存在时,抛出 空指针异常。
参数为 -1 时,抛出自定义异常并处理。
查询结果为 null 时,抛出自定义异常并处理。
查询成功时,正确处理并返回。
@GetMapping("selectOne")
public Result selectOne(Integer id) {
Emp emp = this.empService.queryById(id);
if (id == null) {
throw new NullPointerException();
}
if (id == -1) {
throw new GlobalException("参数异常", 400);
}
if (emp == null) {
throw new GlobalException("未查询到结果,请确认输入是否正确");
}
return Result.ok().data("items", emp).message("查询成功");
}
统一结果处理
- 也就是固定后端需要返回的数据格式
- 具体可看
- https://www.cnblogs.com/l-y-h/p/12781586.html#:~:text=%E5%9B%9E%E5%88%B0%E9%A1%B6%E9%83%A8-,%E4%BA%8C%E3%80%81Java%20%E9%A1%B9%E7%9B%AE%E6%9E%84%E5%BB%BA%20%2D%2D%20%E7%BB%9F%E4%B8%80%E7%BB%93%E6%9E%9C%E5%A4%84%E7%90%86,-1%E3%80%81%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BD%BF%E7%94%A8
- 具体可看
数据格式?
- 是否响应成功(success: true / false)
- 响应状态码(code:200 / 400 / 500 等)
- 状态码描述(message:访问成功 / 系统异常等)
- 响应数据(data:处理的数据)
如何处理
-
success 设置成
Boolean
类型。 -
code 设置成
Integer
类型。 -
message 设置成
String
类型。 -
data 设置成
HashMap
类型。 -
构造器私有,且使用静态方法返回类对象。
-
采用链式调用(即方法返回对象为其本身,return thi
-
所以打算直接拿这位博主的来~
- https://www.cnblogs.com/l-y-h/p
- 记得添加依赖
dependency> groupId>org.apache.httpcomponentsgroupId> artifactId>httpcoreartifactId> version>4.4.16version> dependency>
package com.lyh.common.util;
import lombok.Data;
import org.apache.http.HttpStatus;
import java.util.HashMap;
import java.util.Map;
/**
* 统一结果返回类。方法采用链式调用的写法(即返回类本身 return this)。
* 构造器私有,不允许进行实例化,但提供静态方法 ok、error 返回一个实例。
* 静态方法说明:
* ok 返回一个 成功操作 的结果(实例对象)。
* error 返回一个 失败操作 的结果(实例对象)。
*
* 普通方法说明:
* success 用于自定义响应是否成功
* code 用于自定义响应状态码
* message 用于自定义响应消息
* data 用于自定义响应数据
*
* 依赖信息说明:
* 此处使用 @Data 注解,需导入 lombok 相关依赖文件。
* 使用 HttpStatus 的常量表示 响应状态码,需导入 httpcore 相关依赖文件。
*/
@Data
public class Result {
/**
* 响应是否成功,true 为成功,false 为失败
*/
private Boolean success;
/**
* 响应状态码, 200 成功,500 系统异常
*/
private Integer code;
/**
* 响应消息
*/
private String message;
/**
* 响应数据
*/
private MapString, Object> data = new HashMap>();
/**
* 默认私有构造器
*/
private Result(){}
/**
* 私有自定义构造器
* @param success 响应是否成功
* @param code 响应状态码
* @param message 响应消息
*/
private Result(Boolean success, Integer code, String message){
this.success = success;
this.code = code;
this.message = message;
}
/**
* 返回一个默认的 成功操作 的结果,默认响应状态码 200
* @return 成功操作的实例对象
*/
public static Result ok() {
return new Result(true, HttpStatus.SC_OK, "success");
}
/**
* 返回一个自定义 成功操作 的结果
* @param success 响应是否成功
* @param code 响应状态码
* @param message 响应消息
* @return 成功操作的实例对象
*/
public static Result ok(Boolean success, Integer code, String message) {
return new Result(success, code, message);
}
/**
* 返回一个默认的 失败操作 的结果,默认响应状态码为 500
* @return 失败操作的实例对象
*/
public static Result error() {
return new Result(false, HttpStatus.SC_INTERNAL_SERVER_ERROR, "error");
}
/**
* 返回一个自定义 失败操作 的结果
* @param success 响应是否成功
* @param code 响应状态码
* @param message 相应消息
* @return 失败操作的实例对象
*/
public static Result error(Boolean success, Integer code, String message) {
return new Result(success, code, message);
}
/**
* 自定义响应是否成功
* @param success 响应是否成功
* @return 当前实例对象
*/
public Result success(Boolean success) {
this.setSuccess(success);
return this;
}
/**
* 自定义响应状态码
* @param code 响应状态码
* @return 当前实例对象
*/
public Result code(Integer code) {
this.setCode(code);
return this;
}
/**
* 自定义响应消息
* @param message 响应消息
* @return 当前实例对象
*/
public Result message(String message) {
this.setMessage(message);
return this;
}
/**
* 自定义响应数据,一次设置一个 map 集合
* @param map 响应数据
* @return 当前实例对象
*/
public Result data(MapString, Object> map) {
this.data.putAll(map);
return this;
}
/**
* 通用设置响应数据,一次设置一个 key - value 键值对
* @param key 键
* @param value 数据
* @return 当前实例对象
*/
public Result data(String key, Object value) {
this.data.put(key, value);
return this;
}
}
前端传参校验
-
首先需要添加
spring-boot-starter-validation
-
教程
- https://reflectoring.io/bean-validation-with-spring-boot/
- https://blog.csdn.net/sx_huangying/article/details/90174354
-
具体引入的文档
- https://blog.csdn.net/ityqing/article/details/134381520 (推荐)
Post用法
- 在bean层添加对应注解
public class demo{
@NotNull(message="用户id不能为空")
private Long userId;
@NotBlank(message="用户名不能为空")
private String userName;
@NotBlank(message="年龄不能为空")
private String age;
}
-
在controller层添加
-
@Valid
注解用于告诉 Spring Boot 对MyRequest
对象进行验证
@PostMapping("/xxx") public String createDemo(@RequestBody @Valid Demo demo, BindingResult result){ if(result.hasErrors()) return result.getFieldError().getDefaultMessage(); return "sucess"; }
-
Get用法
-
注意,如果需要使用校验,那么就需要关闭必填项
@RequestParam(value = "username",required = false)
,否则就会出现org.springframework.web.bind.MissingServletRequestParameterException: Required request parameter 'username' for method parameter type String is not present
,也就是字段未填写的报错 -
controller代码
@GetMapping("/test2")
public String getUserStr(
@RequestParam(value = "username",required = false) @NotNull(message = "名字不能为空") String name) {
return "success";
}
- 测试
登录态
- 这里就用jwt来生成一个token并添加在token里面
- jwt依赖
dependency>
groupId>io.jsonwebtokengroupId>
artifactId>jjwt-apiartifactId>
version>0.12.5version>
dependency>
- 使用教程
- https://blog.csdn.net/qq_45365516/article/details/136017102
- 可以参考上面这位人的代码
/**
* jwt工具类
*/
public class JwtUtil {
private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);
private static final String SECRET = "S/4AN9IsSRUC~{0c4]y#$F2XbV8^`#a14vawn;
private static final long defaultExpire = 1000 * 60 * 60 * 24 * 7L;//7天
//创建一个jwt密钥 加密和解密都需要用这个玩意
private static final SecretKey key = Jwts.SIG.HS256.key()
.random(new SecureRandom(SECRET.getBytes(StandardCharsets.UTF_8)))
.build();
private J服务器托管wtUtil() {
}
/**
* 使用默认过期时间(7天),生成一个JWT
*
* @param username 用户名
* @param claims JWT中的数据
* @return
*/
public static String createToken(String username, MapString, Object> claims) {
return createToken(username, claims, defaultExpire);
}
/**
* 生成token
*
* @param username 用户名
* @param claims 请求体数据
* @param expire 过期时间 单位:毫秒
* @return token
*/
public static String createToken(String username, MapString, Object> claims, Long expire) {
JwtBuilder builder = Jwts.builder();
Date now = new Date();
// 生成token
builder.id("rQRk$yN:7%*Bw}A_A-]M~4#;yGa:a_F{") //id 这个可以不填,但是建议填
.issuer("Galaxy") //签发者
.claims(claims) //数据
.subject(username) //主题
.issuedAt(now) //签发时间
.expiration(new Date(now.getTime() + expire)) //过期时间
.signWith(key); //签名方式
builder.header().add("JWT", "JSpWdhuPGblNZApVclmX");
return builder.compact();
}
/**
* 解析token
*
* @param token jwt token
* @return Claims
*/
public static Claims claims(String token) {
try {
return Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(token)
.getPayload();
} catch (Exception e) {
if (e instanceof ExpiredJwtException) {
服务器托管 //现在不需要使用 claims.getExpiration().before(new Date());
// 判断JWT是否过期了 如果过期会抛出ExpiredJwtException异常
throw new RunException("token已过期");
}
if (e instanceof JwtException) {
throw new RunException("token已失效");
}
logger.error("jwt解析失败" + e);
throw new RunException("token解析失败");
}
}
public static void main(String[] args) {
MapString, Object> claims = Map.of("name", "张三");
String token = createToken("mysterious", claims, 3L);
System.out.println(token);
Claims claims1 = claims(token);
System.out.println(claims1);
}
}
token续签?到时续期?
-
https://blog.csdn.net/huang714/article/details/127786678
-
无状态的JWT令牌实现续签功能
- https://blog.csdn.net/panjianlongWUHAN/article/details/122412871
- https://www.cnblogs.com/dancesir/p/17727742.html
- jwt token实现逻辑的核心原理是 前端请求Header中设置的token保持不变,校验有效性以缓存中的token为准,千万不要直接校验Header中的token。实现原理部分大家好好体会一下,思路比实现更重要!
-
大概流程都是2个token,一个refreshToken,当token过期,询问refreshToken是否过期,refreshToken如果没有则派发或者续签证书
redis
- https://juejin.cn/post/7076244567569203208#heading-5
jwt过期判断
- 参考文章
- https://segmentfault.com/a/1190000044223895
void testJWT1(){
String token = "eyJhbGciOiJIUzI1NiIsInR5cGUiOiJKV1QiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjE3MTE1Mjg2NjYsImV4cCI6MTcxMTUyODY3NiwianRpIjoiM2RkN2IzNmUtNjQxMS00OGEzLTkwMDMtZDVhZGVlZWQ1OTBhIiwic3ViIjoiYXV0aCIsInVzZXJOYW1lIjoicWl1eWUiLCJpZCI6MTIzfQ.lPbcvtgbI4VEhQZrkhmws-zmZLXlQrysRlAmFnNsVeU";
try {
JWTValidator jwtValidator = JWTValidator.of(token).validateDate(DateUtil.date());
} catch (ValidateException exception) {
throw new JWTException("token已过期");
}
}
登录注册
用户注册(需返回主键)
- 参考博客
- https://blog.csdn.net/qq_36940798/article/details/77961461
分页功能
- 参考文章
@Test
//测试分页
void testPage(){
PageHelper.startPage(1,10);
ListArticleInfo> articleInfos = articleInfoMapper.articleList();
PageInfoArticleInfo> articleInfoPageInfo = new PageInfo>(articleInfos);
}
- 下图展示的是
articleInfoPageInfo
的信息
问题
-
cn.hutool.core.convert.NumberWithFormat cannot be cast to java.lang.Integer
- 使用Integer.parseInt方法转换
-
需要的类型:Supplier提供的类型:String(Required type: Supplier <java.lang.String> Provided: String)
- 导错误包了,应该是slf4j的
// import org.mybatis.logging.Logger; // import org.mybatis.logging.LoggerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory;
-
mybatis中的@param什么时候加什么时候不加呢
- https://blog.csdn.net/qq_39187593/article/details/133868321
-
@RequestBody
通常,在处理 HTTP POST 请求时,客户端将请求的数据作为请求体发送到服务器端。服务器端可以使用@RequestBody
注解将请求体的内容绑定到方法的参数上,以便进行处理。- 源于https://blog.csdn.net/qq_44543774/article/details/133311163
在上述示例中,@RequestBody
注解应用于 User
对象的参数,表示将请求体的内容绑定到 User
对象上。这样,当客户端发送一个包含用户信息的 JSON 请求体时,Spring 框架会自动将该 JSON 数据转换为 User
对象,并将其作为参数传递给 createUser
方法。
需要注意的是,使用 @RequestBody
注解时,Spring 框架会使用消息转换器(MessageConverter)来处理请求体的数据转换。默认情况下,Spring 支持多种消息转换器,包括处理 JSON、XML、Form 表单等数据格式。
总结来说,@RequestBody
注解用于将 HTTP 请求的内容绑定到方法的参数上,方便在 Spring 控制器中处理请求体的数据。
@PostMapping("/users")
public ResponseEntity createUser(@RequestBody User user) {
// 处理创建用户的逻辑
}
- @ResponseBody||@RequestBoby||@requestParam的注解使用和注意事项
- 源于https://blog.csdn.net/FighterSnail/article/details/122875137
总结
@requestParam
1.用来获取URL后面追加的参数
2.POST请求,content-type:application/json 的body中的参数
@requestBoby
1.接收POST ,content-type:application/json的body参数 (后端一般封装成javaBean对象处理)
-
RequestBody 'application/x-www-form-urlencoded;charset=UTF-8' not supported
- 具体可看https://blog.csdn.net/cnds123321/article/details/118184858
问题代码
@PostMapping( "/register")
public Result register(@RequestBody UserInfo userInfo){
log.info(userInfo.toString());
return Result.ok();
}
前端接口Content-Type:application/x-www-form-urlencoded
原因
前端请求传Json对象的字符串则后端才使用@RequestBody。而我前端采用的表单提交的数据,是不能采用@RequestBody注解的。
解决办法:
第一种解决方式就是修改后端代码,去掉@RequestBody注解,也可以直接获取到表单提交的POST数据。
//修改后
@PostMapping( "/register")
public Result register(UserInfo userInfo){
log.info(userInfo.toString());
return Result.ok();
}
第二种解决方式就是修改前端代码,前端传递JSON对象的字符串,这里我采用jQuery来发送ajax请求传递JSON对象的字符串数据。
指定contentType: "application/json",
- 过滤器与拦截器
- https://blog.csdn.net/weixin_63739578/article/details/130204734
- 实现多端登录+token自动续期和定期刷新+自定义注解和拦截器实现鉴权(角色和权限校验)
- https://blog.csdn.net/weixin_43165220/article/details/128404956
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net
一个服务端进程最大能支持多少条 TCP 连接? 一台服务器最大能支持多少条 TCP 连接? 一、原理 TCP 四元组的信息:源IP、源端口、目标IP、目标端口。 一个服务端进程最大能支持的 TCP服务器托管网 连接个数的计算公式:最大tcp连接数=客户端的IP…