java每日精进1.16(新增用户)

1.controller层

@PostMapping("/create")
    @Operation(summary = "新增用户")
    @PreAuthorize("@ss.hasPermission('system:user:create')")
    public CommonResult createUser(@Valid @RequestBody UserSaveReqVO reqVO) {
        Long id = userService.createUser(reqVO);
        return success(id);
    }

@Operation

这是 Swagger 的注解,提供 API 文档的描述。

@PreAuthorize("@ss.hasPermission('system:user:create')")

Spring Security 的注解,用于权限控制。
public CommonResult createUser(@Valid @RequestBody UserSaveReqVO reqVO)

@Valid:用于启用 JSR-303/JSR-380 的验证机制,表示请求体中的参数会根据 UserSaveReqVO 类中定义的校验规则进行校验。比如,@NotBlank@Size 等校验注解会生效,若校验失败会抛出 MethodArgumentNotValidException 异常,返回错误信息给客户端。

2.vo

@Schema(description = "管理后台 - 用户创建/修改 Request VO")
@Data
public class UserSaveReqVO {

    @Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao")
    @NotBlank(message = "用户账号不能为空")
    @Pattern(regexp = "^[a-zA-Z0-9]{4,30}$", message = "用户账号由 数字、字母 组成")
    @Size(min = 4, max = 30, message = "用户账号长度为 4-30 个字符")
    @DiffLogField(name = "用户账号")
    private String username;

    @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
    @Length(min = 4, max = 16, message = "密码长度为 4-16 位")
    private String password;

    @AssertTrue(message = "密码不能为空")
    @JsonIgnore
    public boolean isPasswordValid() {
        return id != null // 修改时,不需要传递
                || (ObjectUtil.isAllNotEmpty(password)); // 新增时,必须都传递 password
    }

}

上文@Valid注解,会根据 UserSaveReqVO 类中定义的校验规则进行校验;

@Schema : Swagger/OpenAPI 用于描述模型类字段的注解,它通常用于生成 API 文档中字段的描述。requiredMode = Schema.RequiredMode.REQUIRED 用于标记该字段为必填字段,表示在生成的 API 文档中,该字段是必填的。

@NotBlank:确保 username不能为空,若为空则抛出自定义消息 "用户账号不能为空"。
@Pattern:限制用户名的格式,regexp属性指定了一个正则表达式,要求用户名只能由 字母和数字 组成,且长度在 4 到 30 个字符之间。
@Size:限制用户名的长度为 4 到 30 个字符。
@DiffLogField:这是一个自定义注解,通常用于日志记录。它标记该字段需要在日志中记录变更信息,并且 name参数用于设置字段的名称,以便日志更具可读性。
@AssertTrue:这个注解用于验证方法的返回值必须为 true,如果验证失败,将抛出错误信息 "密码不能为空"
@JsonIgnore:这个注解表示 isPasswordValid方法的返回值不会被序列化为 JSON 数据。它通常用于那些在传输时不需要的验证方法。
  • 字段验证:如 @NotBlank@Size@Email@Pattern 等注解用于验证字段输入是否合法。
  • 日志记录:使用 @DiffLogField 来记录字段的变化,确保在变更时能够记录相关的日志信息。
  • 密码验证:通过 isPasswordValid() 方法确保新增用户时必须提供密码,而更新时则不需要。

3.common.validation(自定义注解)

@Target({
        ElementType.METHOD,
        ElementType.FIELD,
        ElementType.ANNOTATION_TYPE,
        ElementType.CONSTRUCTOR,
        ElementType.PARAMETER,
        ElementType.TYPE_USE
})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
        validatedBy = MobileValidator.class
)
public @interface Mobile {

    String message() default "手机号格式不正确";

    Class[] groups() default {};

    Class[] payload() default {};

}

自定义注解 Mobile,用于验证手机号的格式是否正确。这个注解本质上是一个约束(constraint),用来在 Java Bean Validation 框架中进行自定义验证。

  • @Target 注解定义了 @Mobile 注解可以应用的 Java 元素类型。这里表示它可以应用于以下元素:
    • ElementType.METHOD:方法。
    • ElementType.FIELD:字段。
    • ElementType.ANNOTATION_TYPE:注解类型。
    • ElementType.CONSTRUCTOR:构造方法。
    • ElementType.PARAMETER:方法参数。
    • ElementType.TYPE_USE:类型使用(如泛型类型、局部变量等)。

这意味着你可以在类的字段、方法参数、构造方法、注解类型等地方使用 @Mobile 注解。

@Retention 定义了该注解的生命周期。在这里,RUNTIME 表示该注解会在运行时被保留,因此可以在运行时通过反射获取到 @Mobile 注解的信息。@Mobile 注解在运行时可以用于执行验证。

@Documented 表示该注解会出现在 Javadoc 文档中。如果将 @Mobile 应用于某个类或字段,它会出现在该类或字段的 Javadoc 中。

message 属性是一个字符串,用来定义验证失败时的错误消息。默认值是 "手机号格式不正确"。当手机号格式验证失败时,框架会返回这个错误消息。

groups 属性用于分组验证。通过指定不同的分组,可以在不同的场景下执行不同的验证。例如,在创建时验证某些字段,在更新时验证其他字段。

payload 属性用于携带额外的元数据。它可以用于传递一些信息给验证器,通常情况下,这个字段可以为空。它通常与集成第三方系统时使用,或者是用来附加附加的信息给验证器类。

public class MobileValidator implements ConstraintValidator {

    @Override
    public void initialize(Mobile annotation) {
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // 如果手机号为空,默认不校验,即校验通过
        if (StrUtil.isEmpty(value)) {
            return true;
        }
        // 校验手机
        return ValidationUtils.isMobile(value);
    }

}
  • Mobile:是该校验器适用于的注解类型。
  • String:表示该校验器适用于字符串类型的字段(即手机号字段是字符串类型)。
  • initialize(Mobile annotation):这是 ConstraintValidator 接口中的方法。它在校验器创建时被调用,可以在这里进行初始化操作。对于 @Mobile 注解,不需要做任何初始化工作,因此该方法为空。
  • isValid(String value, ConstraintValidatorContext context):这是 ConstraintValidator 接口中的核心方法,实际执行验证的地方。
  • value:是被校验的字段值,这里就是传入的手机号(一个字符串)。
  • context:是校验的上下文,包含了校验相关的附加信息,通常用于生成错误信息或报告校验失败等。
private static final Pattern PATTERN_MOBILE = 
Pattern.compile("^(?:(?:\\+|00)86)?1(?:(?:3[\\d])
|(?:4[0,1,4-9])
|(?:5[0-3,5-9])
|(?:6[2,5-7])
|(?:7[0-8])
|(?:8[\\d])
|(?:9[0-3,5-9]))\\d{8}$");

4.Impl

@Override
    @Transactional(rollbackFor = Exception.class)
    @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_CREATE_SUB_TYPE, bizNo = "{{#user.id}}",
            success = SYSTEM_USER_CREATE_SUCCESS)
    public Long createUser(UserSaveReqVO createReqVO) {
        // 1.1 校验账户配合
        tenantService.handleTenantInfo(tenant -> {
            long count = userMapper.selectCount();
            if (count >= tenant.getAccountCount()) {
                throw exception(USER_COUNT_MAX, tenant.getAccountCount());
            }
        });
        // 1.2 校验正确性
        validateUserForCreateOrUpdate(null, createReqVO.getUsername(),
                createReqVO.getMobile(), createReqVO.getEmail(), createReqVO.getDeptId(), createReqVO.getPostIds());
        // 2.1 插入用户
        AdminUserDO user = BeanUtils.toBean(createReqVO, AdminUserDO.class);
        user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
        user.setPassword(encodePassword(createReqVO.getPassword())); // 加密密码
        userMapper.insert(user);
        // 2.2 插入关联岗位
        if (CollectionUtil.isNotEmpty(user.getPostIds())) {
            userPostMapper.insertBatch(convertList(user.getPostIds(),
                    postId -> new UserPostDO().setUserId(user.getId()).setPostId(postId)));
        }

        // 3. 记录操作日志上下文
        LogRecordContext.putVariable("user", user);
        return user.getId();
    }

@LogRecord:自定义的注解,用于记录操作日志。日志记录的内容包括:

  • type:操作的类型(如 SYSTEM_USER_TYPE)。
  • subType:子类型(如 SYSTEM_USER_CREATE_SUB_TYPE)。
  • bizNo:业务编号,这里通过 {{#user.id}} 动态获取用户的 id 值。
  • success:操作是否成功,这里是常量 SYSTEM_USER_CREATE_SUCCESS
  • tenantService.handleTenantInfo:处理租户信息,这里是通过 tenant 来获取租户的账户信息。
  • userMapper.selectCount():查询当前用户表中已有用户的数量。
  • if (count >= tenant.getAccountCount()):如果当前已有用户数量超过租户允许的最大账户数,则抛出异常,阻止创建新用户。
private AdminUserDO validateUserForCreateOrUpdate(Long id, String username, String mobile, String email,
                                                      Long deptId, Set postIds) {
        // 关闭数据权限,避免因为没有数据权限,查询不到数据,进而导致唯一校验不正确
        return DataPermissionUtils.executeIgnore(() -> {
            // 校验用户存在
            AdminUserDO user = validateUserExists(id);
            // 校验用户名唯一
            validateUsernameUnique(id, username);
            // 校验手机号唯一
            validateMobileUnique(id, mobile);
            // 校验邮箱唯一
            validateEmailUnique(id, email);
            // 校验部门处于开启状态
            deptService.validateDeptList(CollectionUtils.singleton(deptId));
            // 校验岗位处于开启状态
            postService.validatePostList(postIds);
            return user;
        });
    }

DataPermissionUtils.executeIgnore 是一个工具类,调用该方法的目的是在执行校验时忽略数据权限的限制。通常,数据权限的校验可能导致某些数据查询不到,这里显式关闭数据权限,以确保校验操作能够顺利进行,避免因数据权限问题导致的校验失败。

通过后则数据都正确,可以进行插入;

  • BeanUtils.toBean(createReqVO, AdminUserDO.class):使用 BeanUtils 工具将请求对象(createReqVO)转换为数据库实体类对象(AdminUserDO)。
  • user.setStatus(CommonStatusEnum.ENABLE.getStatus()):设置用户的状态为启用,假设 CommonStatusEnum.ENABLE.getStatus() 返回的是用户启用的状态值。
  • user.setPassword(encodePassword(createReqVO.getPassword())):对传入的密码进行加密,保证安全性。
  • userMapper.insert(user):将用户信息插入到数据库中。
  • user.getPostIds():获取用户关联的岗位 ID 列表。
  • if (CollectionUtil.isNotEmpty(user.getPostIds())):判断岗位 ID 列表是否为空,只有当该列表非空时,才会执行插入操作。
  • userPostMapper.insertBatch(...):批量插入用户与岗位的关联关系,使用 convertList 方法将岗位 ID 列表转换为 UserPostDO 对象,然后插入到 user_post 表中。
  • LogRecordContext.putVariable("user", user):将创建的 user 对象放入日志记录上下文中,方便后续的日志记录使用。
  • 校验租户的账户配额,防止超过最大账户数。
  • 校验用户的基本信息(如用户名、手机号、邮箱等)。
  • 插入用户信息并加密密码。
  • 如果用户关联了岗位,插入用户与岗位的关联数据。
  • 记录操作日志。
  • 返回新创建用户的 ID。

你可能感兴趣的:(SpringCloud功能,java,微服务,spring,spring,cloud)