【SpringBoot】 黑马大事件笔记-day2

目录

用户部分

实体类属性的参数校验

更新用户密码

文章部分

规定josn日期输出格式

分组校验


上期回顾:【SpringBoot】 黑马大事件笔记-day1

用户部分


实体类属性的参数校验

对应的接口文档:

基本信息

请求路径:/user/update

请求方式:PUT

接口描述:该接口用于更新已登录用户的基本信息(除头像和密码)

请求参数

请求参数格式:application/json

请求参数说明:

请求数据样例:

{
"id":5,
"username":"wangba",
"nickname":"wb",
"email":"wb@itcast.cn"
}

响应数据

响应数据类型:application/json

响应参数说明:

响应数据样例:

{
"code": 0,
"message": "操作成功",
"data": null
}

这种其实比较简单,就是底层的增删查改;++明确接口文档的需求:username 不是必传项,而其他属性必须要传。而且传的属性值需要进行校验,确保数据的正确性。++ 比如邮箱的格式需要规范,否则发不了短信找回账号。++其次修改日期需要进行更新。++

Contorller

cs 复制代码
    @RequestMapping("/update")
    public Result update(@RequestBody User user) {
        userService.update(user);
        return Result.success();
    }

Service

我们可以使用LocalDateTime.now() 方法来记录当前系统时间,这样用户信息的更新时间便有了。

cs 复制代码
    @Override
    public void update(Category category) {
        category.setUpdateTime(LocalDateTime.now());
        categoryMapper.update(category);
    }

Mapper

css 复制代码
    <update id="update">
        UPDATE user SET nickname=#{nickname},email=#{email},update_time=#{updateTime} where id=#{id}
    </update>

我们发现参数没有进行校验,容易导致一些错误:用户名有奇怪字符以及邮箱不正确导致发送不了验证码。

所以我们要对参数进行校验:++实体类的成员变量上添加注解++

Pojo

cpp 复制代码
@Data
@AllArgsConstructor
@NoArgsConstructor
public class  User {
    @NotNull
    private Integer id;//主键ID
    private String username;//用户名
    @JsonIgnore 
    private String password;//密码
    @NotEmpty
    @Pattern(regexp = "^\\S{1,10}$")
    private String nickname;//昵称
    @NotEmpty
    @Email
    private String email;//邮箱
    private String userPic;//用户头像地址
    private LocalDateTime createTime;//创建时间
    private LocalDateTime updateTime;//更新时间
}

对实体类的属性添加注解,可以起到一定的约束作用:

注解 作用
@NotNull 不能为 null,但可以为 empty,一般用在 Integer 类型的基本数据类型的非空校验上
@NotEmpty 不能为 null,且长度必须大于 0,一般用在集合类上或者数组上
@Email 用于验证字符串是否符合电子邮件的格式,一般应用于 String 类的字段上
@Pattern 被注解的元素必须符合给定的正则表达式,一般用来规定该属性的长度区间

注意: 要使这些注解生效还有一个条件,就是在++控制层传入参数的这里加上 @Validated 注解++

cs 复制代码
    @RequestMapping("/update")
    public Result update(@RequestBody @Validated User user) {
        userService.update(user);
        return Result.success();
    }

这样当我们重新运行项目时,就会抛出校验失败的异常。

更新用户密码

对应的接口文档:

基本信息

请求路径:/user/updatePwd

请求方式:PATCH

接口描述:该接口用于更新已登录用户的密码

请求参数

请求参数格式:application/json

请求参数说明:

请求数据样例:

cs 复制代码
{
"old_pwd":"123456",
"new_pwd":"234567",
"re_pwd":"234567"
}

根据接口文档的说,我们可以知道修改密码需要三个属性:原密码、新密码、确认密码。

所以我们后端需要用 Map 来接收参数;为了保证密码的规范还需要对其进行校验:

|-------------------|
| 密码的长度是否合法,有没有缺少参数 |
| 输入的原密码是否与数据库中的匹配 |
| 新密码与确认密码是否一致 |

只有满足以上条件,密码才能修改成功。

Controller

cs 复制代码
@PatchMapping("/updatePwd")
    public Result updatePwd(@RequestBody Map<String,String> params){
        // 校验参数
        String oldPwd = params.get("old_pwd");
        String newPwd = params.get("new_pwd");
        String rePwd = params.get("re_pwd");

        if(!StringUtils.hasLength(oldPwd) || !StringUtils.hasLength(newPwd) || !StringUtils.hasLength(rePwd)) {
            return Result.error("缺少必要参数");
        }

        // 判断原密码
        Map<String,Object> map =  ThreadLocalUtil.get();
        String username = (String) map.get("username");
        User LoginUser =   userService.findByUserName(username);
        if(!Md5Util.getMD5String(oldPwd).equals(LoginUser.getPassword())) {
            return Result.error("原密码填写不正确");
        }

        // 修改密码和确认密码是否一样
        if(!newPwd.equals(rePwd)) {
            return Result.error("两次填写的密码不一致");
        }

        // 调用Service完成密码更新
        userService.updatePwd(newPwd);
        return Result.success();
    }

由于在数据库中的密码是经过 Md5Util 加密的,所以比较时需要将输入原密码通过 Md5Util 转化后在比较。

Serivce

cs 复制代码
    @Override
    public void updatePwd(String newPwd) {
        Map<String,Object> map = ThreadLocalUtil.get();
        Integer id = (Integer) map.get("id");
        userMapper.updatePwd(Md5Util.getMD5String(newPwd),id);
    }

由于需要修改密码,首先要获取用户的信息;之前在 ThreadLocal 存放的用户信息 id 此时就就可以直接获取。

Mapper

别忘记每次更新数据库数据都需要更新修改时间。

cs 复制代码
     <update id="updatePwd">
        UPDATE user SET password=#{newPwd},update_time=now() WHERE id=#{id}
    </update>

文章部分


规定josn日期输出格式

@JsonFormat 是在 Jackson 中定义的一个注解,是一个时间格式化注解。此注解用于属性上,作用是把 Date 类型的数据转化成为我们想要的格式。

cpp 复制代码
    @JsonFormat(pattern = "yyyy-mm-dd HH:mm:ss")
    private LocalDateTime createTime;//创建时间
    @JsonFormat(pattern = "yyyy-mm-dd HH:mm:ss")
    private LocalDateTime updateTime;//更新时间

分组校验

需求

我们经常会碰到这样的一个场景:

Controller

cpp 复制代码
    @PostMapping
    public Result add(@RequestBody @Validated Category category) {
        categoryService.add(category);
        return Result.success();
    }


    @PutMapping
    public Result update(@RequestBody @Validated Category category) {
        categoryService.update(category);
        return Result.success();
    }

Pojo

cpp 复制代码
    @NotNull
    private Integer id;//主键ID

更新的时候某些字段为必填(比如id), 新增的时候非必填:

Service

cpp 复制代码
    @Override
    public void add(Category category) {
        // 补充属性
        category.setCreateTime(LocalDateTime.now());
        category.setUpdateTime(LocalDateTime.now());
        // 获取用户id
        Map<String,Object> map = ThreadLocalUtil.get();
        Integer id = (Integer) map.get("id");
        category.setCreateUser(id);
        categoryMapper.add(category);
    }


    @Override
    public void update(Category category) {
        category.setUpdateTime(LocalDateTime.now());
        categoryMapper.update(category);
    }

Mapper

cpp 复制代码
    <insert id="add">
        INSERT INTO category(category_name, category_alias, create_user, create_time, update_time)
        VALUES (#{categoryName},#{categoryAlias},#{createUser},#{createTime},#{updateTime})
    </insert>

     <update id="update">
        UPDATE category SET category_name=#{categoryName},category_alias=#{categoryAlias},update_time=#{updateTime}
        WHERE id=#{id}
    </update>

新增的时候只需获取 ThreadLocal 中的用户 id 进行有效的插入即可,Mapper 并不涉及 id 的操作,所以获取请求时不需要传入 id;更新的时候 Mapper 需要 id 进行文章信息的定位,所以获取请求时需要传入 id。++但是我们在 Pojo 给 id 属性加了 @NotNull 注解,表示不能为空;所以新增在获取对象请求的时候必须传入 id 否则就会抛出异常。++

如何解决这种问题呢?Validator 校验框架提供了分组校验,可以帮助我们快速的实现这样的需求。简单来说就是,++新增时使用新增校验规则,更新时使用更新校验规则。++

分组校验
把校验项进行归类分组,在完成不同的功能的时候,校验指定组中的校验项。

步骤:

|---------------|
| 定义分组 |
| 定义校验项时指定归属的分组 |
| 校验时指定要校验的分组 |

定义分组:

我们以在 Pojo 实体类中定义两个接口,说明分了 Add、Update 两个组。

cpp 复制代码
public class Category {

    public interface Add {

    }

    public interface Update {

    }
}

定义校验项时指定归属的分组:

cpp 复制代码
public class Category {
    @NotNull(groups = Update.class)
    private Integer id;
    @NotEmpty(groups= {Add.class,Update.class})
    private String categoryName;
    @NotEmpty(groups= {Add.class,Update.class})
    private String categoryAlias;

    public interface Add {}
    public interface Update {}
}

校验时指定要校验的分组:

cpp 复制代码
    @PostMapping
    public Result addCategory(@RequestBody @Validated(Category.Add.class) Category category) {
        categoryService.add(category);
        return Result.success();
    }


    @PutMapping
    public Result update(@RequestBody @Validated(Category.Update.class) Category category) {
        categoryService.update(category);
        return Result.success();
    }

这样新增时就不需要传入 id 了。

结合 @Validated 源码:我们来看一下

cpp 复制代码
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validated {
    Class<?>[] value() default {};
}

例如:@Validated 注解中增加了 Category.Add.class 参数,表示对于定义了分组校验的字段使用 Add 校验规则,其他字段使用默认规则。

这样就又出现了另一个问题:如果同一个校验项属于多个分组的话,就需要在 groups= {} 中传入多个参数;这样我们就可以使用 @Validated 默认分组来优化这个问题。

举个例子:

如果说某个校验项没有指定分组,默认属于 Default 分组。分组之间可以继承,A extends B 那么 A 中拥有 B 中所有的校验项。

cpp 复制代码
public class Category {
    @NotNull(groups = Update.class)
    private Integer id;
    @NotEmpty
    private String categoryName;
    @NotEmpty
    private String categoryAlias;
   
    public interface Add extends Default {}

    public interface Update extends Default{}
}

所以 @NotEmpty 就相当于 groups= {Add.class,Update.class},而 @NotNull(groups = Update.class) 指定了校验项,所以只有更新的操作才进行校验。

相关推荐
赞哥哥s5 分钟前
MISRA C2012学习笔记(9)-Rules 8.14
笔记·学习·autosar·misra c
baijin_cha5 分钟前
机器学习基础03_特征降维&KNN算法-分类&模型选择和调优
笔记·算法·机器学习
YuCaiH2 小时前
【STM32】USART串口数据包
笔记·stm32·单片机·嵌入式硬件
Daydreamer102 小时前
学习笔记——PLCT汪辰:开发RISC-V上的操作系统(持续更新)
笔记·学习·risc-v
王夏奇3 小时前
C代码—单元测试中的覆盖率—学习笔记
笔记·学习·单元测试
scdifsn3 小时前
动手学深度学习10.1. 注意力提示-笔记&练习(PyTorch)
pytorch·笔记·深度学习·注意力机制·注意力提示
向上的车轮3 小时前
ODOO学习笔记(8):模块化架构的优势
笔记·python·学习·架构
非概念5 小时前
stm32学习笔记----51单片机和stm32单片机的区别
笔记·stm32·单片机·学习·51单片机
青椒大仙KI117 小时前
24/11/13 算法笔记<强化学习> DQN算法
笔记·算法
promise-render7 小时前
npm、yarn、pnpm 切换查看镜像源笔记
前端·笔记·npm