java练习值简易博客的搭建

前言

  • 练习项目,参照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.httpcomponents</groupId>
        <artifactId>httpcore</artifactId>
        <version>4.4.16</version>
    </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 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;
    }
}

前端传参校验

  • 首先需要添加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.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.12.5</version>
</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<~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续签?到时续期?

  • 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);
    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的
    // 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

未成年梦想

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值