java练习值简易博客的搭建

前言

注意

  • 项目为了练习,没有使用mybatisplus的简化写法

统一错误(异常)处理

  • 还是这位博主的博客

  • 使用统一结果处理时,有些异常我们可以提前预知并处理,但是一个运行时异常,我们不一定能预知并处理,这时可以使用统一异常处理,当异常发生时,触发该处理操作,从而保证程序的健壮性。

  • 使用 @ControllerAdvice 或者 @RestControllerAdvice 注解作为统一异常处理的核心。

    • 这两个注解都是 Spring MVC 提供的。作用于 控制层 的一种切面通知。

      【@ControllerAdvice 与 @RestControllerAdvice 区别:】
      @RestControllerAdvice 注解包含了 @ControllerAdvice 与 @ResponseBody 注解。
      类似于 @Controller 与 @RestController 的区别。
      @RestControllerAdvice 返回 json 数据时不需要添加 @ResponseBody 注解。

自定义一个异常类,用于处理项目中的异常,并收集异常信息。

java 复制代码
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 注解去捕获异常。
java 复制代码
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 时,抛出自定义异常并处理。
    查询成功时,正确处理并返回。
java 复制代码
@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("查询成功");
}

统一结果处理

数据格式?

  • 是否响应成功(success: true / false)
  • 响应状态码(code:200 / 400 / 500 等)
  • 状态码描述(message:访问成功 / 系统异常等)
  • 响应数据(data:处理的数据)

如何处理

  • success 设置成 Boolean 类型。

  • code 设置成 Integer 类型。

  • message 设置成 String 类型。

  • data 设置成 HashMap 类型。

  • 构造器私有,且使用静态方法返回类对象。

  • 采用链式调用(即方法返回对象为其本身,return thi

  • 所以打算直接拿这位博主的来~

    xml 复制代码
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpcore</artifactId>
        <version>4.4.16</version>
    </dependency>
java 复制代码
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 Map<String, 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(Map<String, 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;
    }
}

前端传参校验

Post用法

  • 在bean层添加对应注解
java 复制代码
public class demo{
    @NotNull(message="用户id不能为空")
    private Long userId;
    @NotBlank(message="用户名不能为空")
    private String userName;
    @NotBlank(message="年龄不能为空")
    private String age;
}
  • 在controller层添加

    • @Valid 注解用于告诉 Spring Boot 对 MyRequest 对象进行验证
    java 复制代码
    @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代码

java 复制代码
@GetMapping("/test2")
public String getUserStr(
        @RequestParam(value = "username",required = false) @NotNull(message = "名字不能为空") String name) {

    return "success";
}
  • 测试

登录态

  • 这里就用jwt来生成一个token并添加在token里面
  • jwt依赖
xml 复制代码
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.12.5</version>
</dependency>
java 复制代码
/**
 * jwt工具类
 */
public class JwtUtil {

    private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);
    private static final String SECRET = "S/4AN9IsSRUC~{0c4]y#$F2XbV8^`#a14vawn<~Kr@(D%3TF-p1s/h{Y9k7y((rR";
    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 JwtUtil() {
    }

    /**
     * 使用默认过期时间(7天),生成一个JWT
     *
     * @param username 用户名
     * @param claims   JWT中的数据
     * @return
     */
    public static String createToken(String username, Map<String, Object> claims) {
        return createToken(username, claims, defaultExpire);
    }

    /**
     * 生成token
     *
     * @param username 用户名
     * @param claims   请求体数据
     * @param expire   过期时间 单位:毫秒
     * @return token
     */
    public static String createToken(String username, Map<String, 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) {
        Map<String, Object> claims = Map.of("name", "张三");
        String token = createToken("mysterious", claims, 3L);
        System.out.println(token);
        Claims claims1 = claims(token);
        System.out.println(claims1);
    }
}

token续签?到时续期?

redis

jwt过期判断

java 复制代码
void testJWT1(){
    String token = "eyJhbGciOiJIUzI1NiIsInR5cGUiOiJKV1QiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjE3MTE1Mjg2NjYsImV4cCI6MTcxMTUyODY3NiwianRpIjoiM2RkN2IzNmUtNjQxMS00OGEzLTkwMDMtZDVhZGVlZWQ1OTBhIiwic3ViIjoiYXV0aCIsInVzZXJOYW1lIjoicWl1eWUiLCJpZCI6MTIzfQ.lPbcvtgbI4VEhQZrkhmws-zmZLXlQrysRlAmFnNsVeU";
    try {
        JWTValidator jwtValidator = JWTValidator.of(token).validateDate(DateUtil.date());
    } catch (ValidateException exception) {
        throw new JWTException("token已过期");
    }
}

登录注册

用户注册(需返回主键)

分页功能

  • 参考文章
java 复制代码
@Test
//测试分页
void testPage(){
    PageHelper.startPage(1,10);
    List<ArticleInfo> articleInfos = articleInfoMapper.articleList();
    PageInfo<ArticleInfo> articleInfoPageInfo = new PageInfo<>(articleInfos);
}
  • 下图展示的是articleInfoPageInfo的信息

问题

  • cn.hutool.core.convert.NumberWithFormat cannot be cast to java.lang.Integer

    • 使用Integer.parseInt方法转换
  • 需要的类型:Supplier<java.lang.String>提供的类型:String(Required type: Supplier <java.lang.String> Provided: String)

    • 导错误包了,应该是slf4j的
    java 复制代码
    // import org.mybatis.logging.Logger;
    // import org.mybatis.logging.LoggerFactory;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
  • mybatis中的@param什么时候加什么时候不加呢

  • @RequestBody通常,在处理 HTTP POST 请求时,客户端将请求的数据作为请求体发送到服务器端。服务器端可以使用@RequestBody 注解将请求体的内容绑定到方法的参数上,以便进行处理。

在上述示例中,@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对象处理)

java 复制代码
问题代码
@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",
	
相关推荐
hccee12 分钟前
C# IO文件操作
开发语言·c#
Viktor_Ye15 分钟前
高效集成易快报与金蝶应付单的方案
java·前端·数据库
hummhumm17 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
一二小选手21 分钟前
【Maven】IDEA创建Maven项目 Maven配置
java·maven
J老熊27 分钟前
JavaFX:简介、使用场景、常见问题及对比其他框架分析
java·开发语言·后端·面试·系统架构·软件工程
猿java32 分钟前
什么是 Hystrix?它的工作原理是什么?
java·微服务·面试
AuroraI'ncoding33 分钟前
时间请求参数、响应
java·后端·spring
zmd-zk41 分钟前
flink学习(2)——wordcount案例
大数据·开发语言·学习·flink
好奇的菜鸟1 小时前
Go语言中的引用类型:指针与传递机制
开发语言·后端·golang
所待.3831 小时前
JavaEE之线程初阶(上)
java·java-ee