云图库平台(三)——后端用户模块开发

news2024/12/25 6:23:01

需求分析:对于用户模块而言,通常要实现下列功能:

  • 用户注册:用户输入账号、密码、确认密码进行注册账号
  • 用户登录:用户通过输入账号、密码登录注册账号
  • 获取当前登录用户信息:即得到当前已登录用户的信息
  • 用户注销/退出:用户可以退出登录
  • 用户权限控制:一般分为管理员和普通用户
  • 用户管理:这里是针对管理员而言,可以对用户进行管理,比如删除、搜索用户等

目录

  • 一、方案设计
    • 数据库表设计
    • 用户登录流程
    • 对用户进行权限控制
  • 二、后端开发
    • 数据访问层代码生成
    • 数据模型开发
      • 实体类
      • 枚举类
  • 三、用户模块接口开发
    • 用户注册
      • 数据模型
      • 服务开发
      • 接口开发
      • 接口测试
    • 用户登录
      • 数据模型
      • 服务开发
      • 接口开发
      • 测试
    • 获取当前登录用户
      • 服务开发
      • 接口开发
      • 数据脱敏
    • 用户注销
      • 服务开发
      • 接口开发
  • 四、用户权限控制
    • 权限校验注解
    • 权限校验切面
    • 注解测试
  • 五、用户管理
    • 数据模型
    • 服务开发
    • 接口开发
    • 分页功能修复
    • 数据精度修复

一、方案设计

数据库表设计

数据库名:cloud-picture

表名:user

user表的核心是用户登录凭证(账号密码)和个人信息,SQL建表语句如下:

-- 用户表
create table if not exists user
(
    id           bigint auto_increment comment 'id' primary key,
    userAccount  varchar(256)                           not null comment '账号',
    userPassword varchar(512)                           not null comment '密码',
    userName     varchar(256)                           null comment '用户昵称',
    userAvatar   varchar(1024)                          null comment '用户头像',
    userProfile  varchar(512)                           null comment '用户简介',
    userRole     varchar(256) default 'user'            not null comment '用户角色:user/admin',
    editTime     datetime     default CURRENT_TIMESTAMP not null comment '编辑时间',
    createTime   datetime     default CURRENT_TIMESTAMP not null comment '创建时间',
    updateTime   datetime     default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
    isDelete     tinyint      default 0                 not null comment '是否删除',
    UNIQUE KEY uk_userAccount (userAccount),
    INDEX idx_userName (userName)
) comment '用户' collate = utf8mb4_unicode_ci;

注意事项如下:

  • updateTimeeditTime区别:updateTime表示这条用户记录任何字段发生的更新时间(由数据库自动更新);而eixtTime则表示用户编辑个人信息的时间(需要我们编写业务代码来进行实现);两者相比updateTime会更容易改变,比如我们直接从数据库中对id值进行改变,此时updateTime发生了改变,而editTime没有发生改变。初始的默认时间都是创建的时间(即default CURRENT_TIMESTAMP)。
  • 给唯一值添加唯一键(唯一索引):比如userAccount,这样可以利用数据库防重复,可以大大提高查询效率。
  • 给经常用户查询、区分度比较的的字段添加索引:比如说可以给userName用户名称添加索引。

用户登录流程

对用户进行权限控制

用户权限一般分为如下4中:

  • 未登录但也可以使用(最为宽松的一种方式)
  • 只有登录用户才可以使用
  • 未登录也可以使用,但是登录用户可以进行更多的操作
  • 管理员可以进行更多的操作

传统的用户权限校验的方式是:在每个接口单独编写逻辑,先获取到当前登录用户的信息,然后判断用户的权限是否符合要求。这种传统的方式灵活但是会写很多的代码,而且开发者无法一眼得知接口需要哪些权限。

权限校验是一个比较通用的业务逻辑,一般会通过Spring AOP切面自定义权限校验注解,如果有特殊的逻辑的话再单独往接口中添加相应的逻辑代码即可。

二、后端开发

建议每次进行接口开发时,都遵循下面的流程:

数据访问层代码生成

首先在idea中连接数据库,然后执行SQL脚本创建数据库表。数据访问层的代码一般包括实体类Mybatis中的Mapper类和XML等。这里使用MyBatisX代码生成插件来快速得到这些文件。

在这里插入图片描述

使用步骤如下:
第一步,选中数据库中的表,右键选择MybatisX中的生成器:
在这里插入图片描述
配置如下,然后点击next:
在这里插入图片描述
在这里插入图片描述
生成成功:
在这里插入图片描述
可以看到生成的代码中包括实体类MapperService
在这里插入图片描述
现在需要将上述生成的代码移到项目中对应的位置,比如将Mapper移动到mapper包、User移动到model.entity包、Service移动到service包

数据模型开发

实体类

使用MybatisX插件生成的代码不能完全满足我们的要求,比如数据库实体类User.java,还需要我们手动更改其字段配置,指定主键策略和逻辑删除。

  • id默认是连续生成的,容易被爬虫抓取,所以更换策略为ASSIGN_ID雪花算法生成。
  • 数据删除时认为彻底删除记录,为了防止出现误删,所以采用逻辑删除——即通过修改isDelete字段为1表示已失效的数据。

修改User.java中的部分内容,如下:
在这里插入图片描述
在这里插入图片描述

修改的代码如下所示:

@TableName(value ="user")
@Data
public class User implements Serializable {
    /**
     * id(要指定主键策略)
     */
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;

    // ...
    
    /**
     * 是否删除(逻辑删除)
     */
    @TableLogic
    private Integer isDelete;
}

可以参照MyBatis Plus的主键生成注解官方文档进行学习。

枚举类

对于用户角色这样值的数量有限的、可枚举的字段,最好新建一个枚举类,在model.enums包下新建UserRoleEnum.java文件,该文件代码如下:

@Getter
public enum UserRoleEnum {

    USER("用户", "user"),
    ADMIN("管理员", "admin");

    private final String text;

    private final String value;

    UserRoleEnum(String text, String value) {
        this.text = text;
        this.value = value;
    }

    /**
     * 根据 value 获取枚举
     *
     * @param value 枚举值的value
     * @return 枚举值
     */
    public static UserRoleEnum getEnumByValue(String value) {
        if (ObjUtil.isEmpty(value)) {
            return null;
        }
        for (UserRoleEnum anEnum : UserRoleEnum.values()) {
            if (anEnum.value.equals(value)) {
                return anEnum;
            }
        }
        return null;
    }
}

上述代码中,getEnumByValue是通过value来找到具体的枚举对象。当然如果枚举的值特别多的话,比如说有成千上万条值的话,可以使用Map缓存所有的值来加速查找。

下面我们正式进入各个接口的开发。

三、用户模块接口开发

用户注册

既然我们要开发接口的话,每个接口要接收前端传来的值,所以每个接口一般要定义一个请求接受类(或者说请求封装类),这样便于对参数进行统一校验和扩展。在model包下创建dtodto表示封装对象,一般用于接收前端传来的值或者是Service之间传递的值,比如说一个服务和另外一个服务的参数需要的是一样的,此时我们就可以定义同一个对象来进行接收。

数据模型

model.dto.user下创建UserRegisterRequest.java(用户注册请求封装类),用于接收请求参数的类:

@Data
public class UserRegisterRequest implements Serializable {

    private static final long serialVersionUID = 3191241716373120793L;

    /**
     * 账号
     */
    private String userAccount;

    /**
     * 密码
     */
    private String userPassword;

    /**
     * 确认密码
     */
    private String checkPassword;
}

接下来开发用户注册服务。

服务开发

下面实现用户注册服务开发:

在Service包下的UserService中添加方法声明:

/**
 * 用户注册
 *
 * @param userAccount   用户账户
 * @param userPassword  用户密码
 * @param checkPassword 校验密码
 * @return 新用户 id
 */
long userRegister(String userAccount, String userPassword, String checkPassword);

UserService中添加l具体的实现代码,同时要多添加一些校验条件:

@Override
public long userRegister(String userAccount, String userPassword, String checkPassword) {
    // 1. 校验
    if (StrUtil.hasBlank(userAccount, userPassword, checkPassword)) {
        throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
    }
    if (userAccount.length() < 4) {
        throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号过短");
    }
    if (userPassword.length() < 8 || checkPassword.length() < 8) {
        throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过短");
    }
    if (!userPassword.equals(checkPassword)) {
        throw new BusinessException(ErrorCode.PARAMS_ERROR, "两次输入的密码不一致");
    }
    // 2. 检查是否重复
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("userAccount", userAccount);
    long count = this.baseMapper.selectCount(queryWrapper);
    if (count > 0) {
        throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复");
    }
    // 3. 加密
    String encryptPassword = getEncryptPassword(userPassword);
    // 4. 插入数据
    User user = new User();
    user.setUserAccount(userAccount);
    user.setUserPassword(encryptPassword);
    user.setUserName("无名");
    user.setUserRole(UserRoleEnum.USER.getValue());
    boolean saveResult = this.save(user);
    if (!saveResult) {
        throw new BusinessException(ErrorCode.SYSTEM_ERROR, "注册失败,数据库错误");
    }
    return user.getId();
}

在上述代码中,我们需要将用户密码加密后进行存储,所以可以封装一个方法,方便后续复用。如下:

@Override
public String getEncryptPassword(String userPassword) {
    // 加盐,混淆密码
    final String SALT = "yf";
    return DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
}

接口开发

在controller包中新建UserController,新增用户注册接口:

@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    private UserService userService;

    /**
     * 用户注册
     */
    @PostMapping("/register")
    public BaseResponse<Long> userRegister(@RequestBody UserRegisterRequest userRegisterRequest) {
        ThrowUtils.throwIf(userRegisterRequest == null, ErrorCode.PARAMS_ERROR);
        String userAccount = userRegisterRequest.getUserAccount();
        String userPassword = userRegisterRequest.getUserPassword();
        String checkPassword = userRegisterRequest.getCheckPassword();
        long result = userService.userRegister(userAccount, userPassword, checkPassword);
        return ResultUtils.success(result);
    }
}

接口测试

现在测试注册用户接口,使用Swagger接口文档来进行测试:

在这里插入图片描述

用户登录

数据模型

model.dto.user下创建UserLoginRequest.java(用户登录请求封装类):

@Data
public class UserLoginRequest implements Serializable {

    private static final long serialVersionUID = 3191241716373120793L;

    /**
     * 账号
     */
    private String userAccount;

    /**
     * 密码
     */
    private String userPassword;
}

接着进行服务开发

服务开发

在Service包下的UserService中增加方法声明:

**/**
 * 用户登录
 *
 * @param userAccount  用户账户
 * @param userPassword 用户密码
 * @param request
 * @return 脱敏后的用户信息
 */
LoginUserVO userLogin(String userAccount, String userPassword, HttpServletRequest request);
**

在UserServiceImpl中增加实现代码,用户登录成功后,将用户信息存储在当前的Session中:

@Override
public LoginUserVO userLogin(String userAccount, String userPassword, HttpServletRequest request) {
    // 1. 校验
    if (StrUtil.hasBlank(userAccount, userPassword)) {
        throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
    }
    if (userAccount.length() < 4) {
        throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号错误");
    }
    if (userPassword.length() < 8) {
        throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误");
    }
    // 2. 加密
    String encryptPassword = getEncryptPassword(userPassword);
    // 查询用户是否存在
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq("userAccount", userAccount);
    queryWrapper.eq("userPassword", encryptPassword);
    User user = this.baseMapper.selectOne(queryWrapper);
    // 用户不存在
    if (user == null) {
        log.info("user login failed, userAccount cannot match userPassword");
        throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在或密码错误");
    }
    // 3. 记录用户的登录态
    request.getSession().setAttribute(USER_LOGIN_STATE, user);
    return this.getLoginUserVO(user);
}

注意注册用户时存入数据库的密码是加密后的,查询用户信息时,也要对用户输入的密码进行同样算法的加密,才能跟数据库的信息对应上。
可以把上述的Session 理解为一个Map,可以给Map设置key和value,每个不同的SessionID对应的Session存储都是不同的,不用担心会污染。所以上述代码中,给Session设置了固定的 key (USER LOGIN STATE),可以将这个key值提取为常量,便于后续获取。

在constant包下新建UserConstant类

/**
 * 用户常量
 */
public interface UserConstant {

    /**
     * 用户登录态键
     */
    String USER_LOGIN_STATE = "user_login";

    //  region 权限

    /**
     * 默认角色
     */
    String DEFAULT_ROLE = "user";

    /**
     * 管理员角色
     */
    String ADMIN_ROLE = "admin";

    // endregion
}

接口开发

在UserController中新增用户登录接口:

@PostMapping("/login")
public BaseResponse<LoginUserVO> userLogin(@RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request) {
    ThrowUtils.throwIf(userLoginRequest == null, ErrorCode.PARAMS_ERROR);
    String userAccount = userLoginRequest.getUserAccount();
    String userPassword = userLoginRequest.getUserPassword();
    LoginUserVO loginUserVO = userService.userLogin(userAccount, userPassword, request);
    return ResultUtils.success(loginUserVO);
}

测试

在这里插入图片描述

获取当前登录用户

这里可以直接从request请求对象对应的Session中直接获取到之前保存的登录用户信息,不需要在请求其它参数。

服务开发

在Service包下的UserService中增加方法声明:

User getLoginUser(HttpServletRequest request);

在UserServiceImpl中实现代码,如果不追求性能的话,最好从数据库中再查一遍,为了保证获取到的数据保持最新。所以先从Session中获取登录用户的id,然后再从数据库中查询到最新的结果。另外,这个方法前端是调用不到的,此方法是给Service之间用的。

@Override
public User getLoginUser(HttpServletRequest request) {
    // 先判断是否已登录
    Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
    User currentUser = (User) userObj;
    if (currentUser == null || currentUser.getId() == null) {
        throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
    }
    // 从数据库查询(追求性能的话可以注释,直接返回上述结果)
    long userId = currentUser.getId();
    currentUser = this.getById(userId);
    if (currentUser == null) {
        throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
    }
    return currentUser;
}

接口开发

在UserController中新增获取当前登录用户接口:

@GetMapping("/get/login")
public BaseResponse<LoginUserVO> getLoginUser(HttpServletRequest request) {
    User loginUser = userService.getLoginUser(request);
    return ResultUtils.success(userService.getLoginUserVO(loginUser));
}

数据脱敏

model.vo包下新增LoginUserVO类,表示脱敏后的登录用户信息:

@Data
public class LoginUserVO implements Serializable {

    /**
     * 用户 id
     */
    private Long id;

    /**
     * 账号
     */
    private String userAccount;

    /**
     * 用户昵称
     */
    private String userName;

    /**
     * 用户头像
     */
    private String userAvatar;

    /**
     * 用户简介
     */
    private String userProfile;

    /**
     * 用户角色:user/admin
     */
    private String userRole;

    /**
     * 创建时间
     */
    private Date createTime;

    /**
     * 更新时间
     */
    private Date updateTime;

    private static final long serialVersionUID = 1L;
}

编写方法对应的实现类,意思就是将User类的属性复制到LoginUserVo中,不存在的字段就被过滤掉了:

@Override
public LoginUserVO getLoginUserVO(User user) {
    if (user == null) {
        return null;
    }
    LoginUserVO loginUserVO = new LoginUserVO();
    BeanUtils.copyProperties(user, loginUserVO);
    return loginUserVO;
}

修改Controller的getLoginUser接口,改为返回脱敏后的用户信息:

@GetMapping("/get/login")
public BaseResponse<LoginUserVO> getLoginUser(HttpServletRequest request) {
    User user = userService.getLoginUser(request);
    return ResultUtils.success(userService.getLoginUserVO(user));
}

用户注销

用户注销和获取当前登录用户一样,都是从request请求对象对应的Session中直接获取到之前保存的登录用户信息,无需其它请求参数。

服务开发

在Service包下的UserService中增加方法声明:

/**
 * 用户注销
 *
 * @param request
 * @return
 */
boolean userLogout(HttpServletRequest request);

在UserServiceImpl中增加实现代码,从Session中移除掉当前用户的登录态即可:

@Override
public boolean userLogout(HttpServletRequest request) {
    // 先判断是否已登录
    Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
    if (userObj == null) {
        throw new BusinessException(ErrorCode.OPERATION_ERROR, "未登录");
    }
    // 移除登录态
    request.getSession().removeAttribute(USER_LOGIN_STATE);
    return true;
}

接口开发

在UserController中新增用户注销接口:

@PostMapping("/logout")
public BaseResponse<Boolean> userLogout(HttpServletRequest request) {
    ThrowUtils.throwIf(request == null, ErrorCode.PARAMS_ERROR);
    boolean result = userService.userLogout(request);
    return ResultUtils.success(result);
}

四、用户权限控制

之前提到过,权限控制是一个比较通用的业务,一般通过Spring AOP切面自定义权限校验注解实现统一的接口拦截和权限校验;如果有特殊的权限校验逻辑的话再单独在接口中编码即可。

权限校验注解

在annotation包中编写权限校验注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthCheck {

    /**
     * 必须有某个角色
     */
    String mustRole() default "";
}

权限校验切面

AOP包下编写权限校验AOP,如下:

@Aspect
@Component
public class AuthInterceptor {

    @Resource
    private UserService userService;

    /**
     * 执行拦截
     *
     * @param joinPoint 切入点
     * @param authCheck 权限校验注解
     */
    @Around("@annotation(authCheck)")
    public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
        String mustRole = authCheck.mustRole();
        RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
        HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
        // 当前登录用户
        User loginUser = userService.getLoginUser(request);
        UserRoleEnum mustRoleEnum = UserRoleEnum.getEnumByValue(mustRole);
        // 不需要权限,放行
        if (mustRoleEnum == null) {
            return joinPoint.proceed();
        }
        // 以下为:必须有该权限才通过
        // 获取当前用户具有的权限
        UserRoleEnum userRoleEnum = UserRoleEnum.getEnumByValue(loginUser.getUserRole());
        // 没有权限,拒绝
        if (userRoleEnum == null) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }
        // 要求必须有管理员权限,但用户没有管理员权限,拒绝
        if (UserRoleEnum.ADMIN.equals(mustRoleEnum) && !UserRoleEnum.ADMIN.equals(userRoleEnum)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }
        // 通过权限校验,放行
        return joinPoint.proceed();
    }
}

注解测试

现在使用该注解(即权限切面检验注解),只要给方法添加了AuthCheck注解,就必须先登录,否则就会抛出异常。使用场景:比如可以给mustRole设置为管理员,这样就仅仅只有管理员才能使用该接口:

@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)

五、用户管理

用户管理功能具体可分为以下几点:

  • 创建用户(管理员)
  • 根据id删除用户(管理员)
  • 更新用户(管理员)
  • 分页获取用户列表(需要脱敏)
  • 根据id获取用户(未脱敏)
  • 根据id获取用户(脱敏)

数据模型

每个操作都需要提供请求类,放到dto.user包下:
在这里插入图片描述

用户创建请求如下:

@Data
public class UserAddRequest implements Serializable {

    /**
     * 用户昵称
     */
    private String userName;

    /**
     * 账号
     */
    private String userAccount;

    /**
     * 用户头像
     */
    private String userAvatar;

    /**
     * 用户简介
     */
    private String userProfile;

    /**
     * 用户角色: user, admin
     */
    private String userRole;

    private static final long serialVersionUID = 1L;
}

用户更新请求如下:

@Data
public class UserUpdateRequest implements Serializable {

    /**
     * id
     */
    private Long id;

    /**
     * 用户昵称
     */
    private String userName;

    /**
     * 用户头像
     */
    private String userAvatar;

    /**
     * 简介
     */
    private String userProfile;

    /**
     * 用户角色:user/admin
     */
    private String userRole;

    private static final long serialVersionUID = 1L;
}

用户查询请求如下(该请求需要继承common中的PageRequest来支持分页查询):

@EqualsAndHashCode(callSuper = true)
@Data
public class UserQueryRequest extends PageRequest implements Serializable {

    /**
     * id
     */
    private Long id;

    /**
     * 用户昵称
     */
    private String userName;

    /**
     * 账号
     */
    private String userAccount;

    /**
     * 简介
     */
    private String userProfile;

    /**
     * 用户角色:user/admin/ban
     */
    private String userRole;

    private static final long serialVersionUID = 1L;
}

还有一个需求就是给普通用户获取到脱敏后的用户信息,和LoginUserVO类似,在model.vo包下新建UserVO类来表示脱敏后的用户信息,如下:

@Data
public class UserVO implements Serializable {

    /**
     * id
     */
    private Long id;
    
    /**
     * 账号
     */
    private String userAccount;

    /**
     * 用户昵称
     */
    private String userName;

    /**
     * 用户头像
     */
    private String userAvatar;

    /**
     * 用户简介
     */
    private String userProfile;

    /**
     * 用户角色:user/admin
     */
    private String userRole;

    /**
     * 创建时间
     */
    private Date createTime;

    private static final long serialVersionUID = 1L;
}

服务开发

  • 在UserService中编写获取脱敏后的单个用户信息,获取脱敏后的用户列表方法:
@Override
public UserVO getUserVO(User user) {
    if (user == null) {
        return null;
    }
    UserVO userVO = new UserVO();
    BeanUtils.copyProperties(user, userVO);
    return userVO;
}

@Override
public List<UserVO> getUserVOList(List<User> userList) {
    if (CollUtil.isEmpty(userList)) {
        return new ArrayList<>();
    }
    return userList.stream().map(this::getUserVO).collect(Collectors.toList());
}
  • 除上述方法外,对于分页查询接口,需要根据用户传入的参数来构造SQL查询。由于使用Mybatis Plus框架,不要要自己拼接SQL了,而是通过构造QueryWrapper对象来生成SQL查询。所以可以在UserService中编写一个方法,专门用于将查询请求转为QueryWrapper对象,如下:

在UserService中新增方法声明:

QueryWrapper<User> getQueryWrapper(UserQueryRequest userQueryRequest);

在UserServiceImpl中编写方法实现:

@Override
public QueryWrapper<User> getQueryWrapper(UserQueryRequest userQueryRequest) {
    if (userQueryRequest == null) {
        throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空");
    }
    Long id = userQueryRequest.getId();
    String userAccount = userQueryRequest.getUserAccount();
    String userName = userQueryRequest.getUserName();
    String userProfile = userQueryRequest.getUserProfile();
    String userRole = userQueryRequest.getUserRole();
    String sortField = userQueryRequest.getSortField();
    String sortOrder = userQueryRequest.getSortOrder();
    QueryWrapper<User> queryWrapper = new QueryWrapper<>();
    queryWrapper.eq(ObjUtil.isNotNull(id), "id", id);
    queryWrapper.eq(StrUtil.isNotBlank(userRole), "userRole", userRole);
    queryWrapper.like(StrUtil.isNotBlank(userAccount), "userAccount", userAccount);
    queryWrapper.like(StrUtil.isNotBlank(userName), "userName", userName);
    queryWrapper.like(StrUtil.isNotBlank(userProfile), "userProfile", userProfile);
    queryWrapper.orderBy(StrUtil.isNotEmpty(sortField), sortOrder.equals("ascend"), sortField);
    return queryWrapper;
}

接口开发

有了上面的基础的代码后,我们就可以开发增删改查的代码了。开发过程中注意添加对应的权限注解,做好参数校验即可,如下:

/**
 * 创建用户
 */
@PostMapping("/add")
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
public BaseResponse<Long> addUser(@RequestBody UserAddRequest userAddRequest) {
    ThrowUtils.throwIf(userAddRequest == null, ErrorCode.PARAMS_ERROR);
    User user = new User();
    BeanUtils.copyProperties(userAddRequest, user);
    // 默认密码 12345678
    final String DEFAULT_PASSWORD = "12345678";
    String encryptPassword = userService.getEncryptPassword(DEFAULT_PASSWORD);
    user.setUserPassword(encryptPassword);
    boolean result = userService.save(user);
    ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
    return ResultUtils.success(user.getId());
}

/**
 * 根据 id 获取用户(仅管理员)
 */
@GetMapping("/get")
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
public BaseResponse<User> getUserById(long id) {
    ThrowUtils.throwIf(id <= 0, ErrorCode.PARAMS_ERROR);
    User user = userService.getById(id);
    ThrowUtils.throwIf(user == null, ErrorCode.NOT_FOUND_ERROR);
    return ResultUtils.success(user);
}

/**
 * 根据 id 获取包装类
 */
@GetMapping("/get/vo")
public BaseResponse<UserVO> getUserVOById(long id) {
    BaseResponse<User> response = getUserById(id);
    User user = response.getData();
    return ResultUtils.success(userService.getUserVO(user));
}

/**
 * 删除用户
 */
@PostMapping("/delete")
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
public BaseResponse<Boolean> deleteUser(@RequestBody DeleteRequest deleteRequest) {
    if (deleteRequest == null || deleteRequest.getId() <= 0) {
        throw new BusinessException(ErrorCode.PARAMS_ERROR);
    }
    boolean b = userService.removeById(deleteRequest.getId());
    return ResultUtils.success(b);
}

/**
 * 更新用户
 */
@PostMapping("/update")
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
public BaseResponse<Boolean> updateUser(@RequestBody UserUpdateRequest userUpdateRequest) {
    if (userUpdateRequest == null || userUpdateRequest.getId() == null) {
        throw new BusinessException(ErrorCode.PARAMS_ERROR);
    }
    User user = new User();
    BeanUtils.copyProperties(userUpdateRequest, user);
    boolean result = userService.updateById(user);
    ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
    return ResultUtils.success(true);
}

/**
 * 分页获取用户封装列表(仅管理员)
 *
 * @param userQueryRequest 查询请求参数
 */
@PostMapping("/list/page/vo")
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
public BaseResponse<Page<UserVO>> listUserVOByPage(@RequestBody UserQueryRequest userQueryRequest) {
    ThrowUtils.throwIf(userQueryRequest == null, ErrorCode.PARAMS_ERROR);
    long current = userQueryRequest.getCurrent();
    long pageSize = userQueryRequest.getPageSize();
    Page<User> userPage = userService.page(new Page<>(current, pageSize),
            userService.getQueryWrapper(userQueryRequest));
    Page<UserVO> userVOPage = new Page<>(current, pageSize, userPage.getTotal());
    List<UserVO> userVOList = userService.getUserVOList(userPage.getRecords());
    userVOPage.setRecords(userVOList);
    return ResultUtils.success(userVOPage);
}

分页功能修复

现在,经过Swagger接口文档对上面接口进行测试后发现,listUserVOByPage接口中分页功能并没有生效,并且查出了全部的数据。

我们使用的Mybatis Plus来操作数据库,可参照官方文档解决。查阅官方文档后发现,需要配置一个分页插件,本项目使用的v3.5.9版本引入分页插件的方式和之前不同,v3.5.9版本需要独立安装分页插件依赖

在这里插入图片描述

pom.xml中引入分页插件的依赖:

<!-- MyBatis Plus 分页插件 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-jsqlparser-4.9</artifactId>
</dependency>

仅仅引入上面这一条大概率是不行的,还需要再pom.xml的依赖管理中补充mybatis-plus-bom,如下:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>${spring-boot.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-bom</artifactId>
            <version>3.5.9</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

依赖下载成功后,在config包下新建Mybatis Plus拦截器配置,添加分页插件,如下:

@Configuration
@MapperScan("com.yf.cloudpicturebackend.mapper")
public class MyBatisPlusConfig {

    /**
     * 拦截器配置
     *
     * @return {@link MybatisPlusInterceptor}
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 分页插件
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

现在重启项目就能正常完成分页功能了。

数据精度修复

现在,再次进行测试,启动项目后F12打开控制台,通过预览查看响应数据,发现了存在精度问题。原因就是前端js的精度范围有限,而后端返回的id范围过大,导致前端精度丢失,会影响前端页面获取到的数据结果。我们可以在后端config包下新建一个全局JSON配置,将整个后端Spring MVC接口返回值的长类型数字转换为字符串进行返回。如下:

/**
 * Spring MVC Json 配置
 */
@JsonComponent
public class JsonConfig {

    /**
     * 添加 Long 转 json 精度丢失的配置
     */
    @Bean
    public ObjectMapper jacksonObjectMapper(Jackson2ObjectMapperBuilder builder) {
        ObjectMapper objectMapper = builder.createXmlMapper(false).build();
        SimpleModule module = new SimpleModule();
        module.addSerializer(Long.class, ToStringSerializer.instance);
        module.addSerializer(Long.TYPE, ToStringSerializer.instance);
        objectMapper.registerModule(module);
        return objectMapper;
    }
}

配置完成重启项目后,此时就没有精度问题了:

在这里插入图片描述

好了,该项目的后端用户模块到这里就结束了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2265144.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

怎么设置电脑密码?Windows和Mac设置密码的方法

为电脑设置密码是保护个人信息安全的重要措施。无论是Windows系统还是MacOS系统&#xff0c;设置密码的步骤都相对简单&#xff0c;但需要根据不同的操作系统选择不同的方法。 一、Windows系统电脑密码设置 方法一&#xff1a;通过控制面板设置账户密码 点击桌面左下角的“开…

思考: 与人交际

前晚可能是因为我和某个曾经的同学&#xff08;我认为是朋友&#xff0c;但是它真的很讨厌&#xff0c;现在觉得它在PUA很多人&#xff09;发生了一件事情&#xff0c;现在没关系了&#xff0c;算是到此结束了&#xff0c;再也不见。 让我看清楚了人和人的交际需要什么&#xf…

突发!!!GitLab停止为中国大陆、港澳地区提供服务,60天内需迁移账号否则将被删除

GitLab停止为中国大陆、香港和澳门地区提供服务&#xff0c;要求用户在60天内迁移账号&#xff0c;否则将被删除。这一事件即将引起广泛的关注和讨论。以下是对该事件的扩展信息&#xff1a; 1. 背景介绍&#xff1a;GitLab是一家全球知名的软件开发平台&#xff0c;提供代码托…

vulnhub靶场-matrix-breakout-2-morpheus攻略(截止至获取shell)

扫描出ip为192.168.121.161 访问该ip&#xff0c;发现只是一个静态页面什么也没有 使用dir dirsearch 御剑都只能扫描到/robots.txt /server-status 两个页面&#xff0c;前者提示我们什么也没有&#xff0c;后面两个没有权限访问 扫描端口&#xff0c;存在81端口 访问&#x…

美股开户网:谷歌搜索迎“大动作”:推出AI模式切换选项应对竞争压力

谷歌的AI战略新举措 近日&#xff0c;硅谷权威媒体《The Information》报道了谷歌即将在其搜索引擎中推出一项全新的功能——“切换到AI模式”。这一新功能将为用户提供更加智能、对话式的回答&#xff0c;标志着谷歌对人工智能&#xff08;AI&#xff09;领域的进一步布局&am…

如何在window 使用 conda 环境下载大模型

最近开始学习 变形金刚&#xff0c;最大的问题就是 huggingface 无法访问&#xff0c;无论是翻墙还是通过本地镜像网站HF-Mirror&#xff0c;然后再通过git下载都很慢&#xff0c;影响学习进度&#xff0c;后面看了如下文章&#xff0c;Huggingface配置镜像_huggingface镜像-CS…

WebRTC学习二:WebRTC音视频数据采集

系列文章目录 第一篇 基于SRS 的 WebRTC 环境搭建 第二篇 基于SRS 实现RTSP接入与WebRTC播放 第三篇 centos下基于ZLMediaKit 的WebRTC 环境搭建 第四篇 WebRTC 学习一&#xff1a;获取音频和视频设备 第五篇 WebRTC学习二&#xff1a;WebRTC音视频数据采集 文章目录 系列文章…

内部知识库的未来展望:技术融合与用户体验的双重升级

在当今数字化飞速发展的时代&#xff0c;企业内部知识库作为知识管理的关键载体&#xff0c;正站在变革的十字路口&#xff0c;即将迎来技术融合与用户体验双重升级的崭新时代&#xff0c;这一系列变化将深度重塑企业知识管理的格局。 一、技术融合&#xff1a;开启知识管理新…

淘宝详情API接口怎么去使用,调用解析

淘宝开放平台提供了丰富的API接口&#xff0c;帮助开发者快速实现与淘宝的交互。其中&#xff0c;淘宝详情API是用于获取商品详情的重要接口之一。通过调用该接口&#xff0c;开发者可以获取到商品的详细信息&#xff0c;如价格、库存、描述等。本文将详细介绍如何调用淘宝详情…

框架程序设计-简答以及论述

目录 maven的pom作用&#xff1a; Pointcut("execution(*com.example.dome.*.*(……))") 缓存的作用&#xff0c;redis配置过程 Redis配置过程&#xff1a; SpringBoot缓存配置过程&#xff1a; AOP的五种增强注解&#xff1a; 论述题&#xff1a;包结构作用、…

【开源库 | xlsxio】C/C++读写.xlsx文件,xlsxio 在 Linux(Ubuntu18.04)的编译、交叉编译

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; ⏰发布时间⏰&#xff1a; 2024-12-20 …

频繁拿下定点,华玉高性能中间件迈入商业化新阶段

伴随着智能驾驶渗透率的快速增长&#xff0c;中国基础软件市场开始进入黄金窗口期。 近日&#xff0c;华玉通软&#xff08;下称“华玉”&#xff09;正式获得某国内头部轨道交通产业集团的智能化中间件平台定点项目。这将是华玉在基础软件领域深耕和商业化发展过程中的又一重…

Mac电脑移动端抓包

*一、相关软件的安装* *1.下载地址* l Fiddler Everywhere 官网&#xff1a;https://www.telerik.com/download/fiddler-everywhere l Charles 官网&#xff1a;https://www.charlesproxy.com/ l Wireshark 官网&#xff1a;https://www.wireshark.org/download.html *…

Spring Boot 动态定时任务管理系统(轻量级实现)

Spring Boot项目中&#xff0c;实现动态增删和启停定时任务的功能对于许多应用场景来说至关重要。虽然Quartz框架是一个广泛使用的解决方案&#xff0c;但其复杂性和重量级特性可能使得项目变得臃肿和难以维护。为了解决这个问题&#xff0c;本项目旨在实现一个轻量级的定时任务…

提高保养效率:4S店预约系统的设计与开发

3.1可行性分析 开发者在进行开发系统之前&#xff0c;都需要进行可行性分析&#xff0c;保证该系统能够被成功开发出来。 3.1.1技术可行性 开发该4S店预约保养系统所采用的技术是vue和MYSQL数据库。计算机专业的学生在学校期间已经比较系统的学习了很多编程方面的知识&#xff…

网络下载ts流媒体

网络下载ts流媒体 查看下载排序合并 很多视频网站&#xff0c;尤其是微信小程序中的长视频无法获取到准确视频地址&#xff0c;只能抓取到.ts片段地址&#xff0c;下载后发现基本都是5~8秒时长。 例如&#xff1a; 我们需要将以上地址片段全部下载后排序后再合成新的长视频。 …

(叁)前端实现加密的方式:AES 加密(实现的两种方式)

前端实现加密&#xff1a; Ⅰ、AES 加密&#xff1a;1、使用 AES 加密前的准备工作&#xff1a;其一、安装 crypto-js &#xff1a;A、安装命令&#xff1a;B、成功安装后的截图&#xff1a; 2、实现 AES 加密的方式一&#xff1a;其一、在项目中创建 crypto.js 文件&#xff1…

kubernates实战

使用k8s来部署tomcat 1、创建一个部署&#xff0c;并指定镜像地址 kubectl create deployment tomcat6 --imagetomcat:6.0.53-jre82、查看部署pod状态 kubectl get pods # 获取default名称空间下的pods kubectl get pods --all-namespaces # 获取所有名称空间下的pods kubect…

自动化测试框架playwright 常见问题和解决方案

自动化课程已经讲完了playwright框架&#xff0c;很多同学跃跃欲试&#xff0c;所谓实践出真知&#xff0c;这不在实践中就要到了一些问题&#xff0c;小编也给大家整理出来了&#xff0c;送个有需要的同学&#xff0c;记得点赞收藏哦~~ 01安装问题 问题描述&#xff1a; 在安…

清华、智谱团队:探索 RLHF 的 scaling laws

基于人类反馈的强化学习&#xff08;RLHF&#xff09;是优化大语言模型&#xff08;LLM&#xff09;行为的关键技术&#xff0c;能够让模型更符合人类偏好和需求&#xff0c;提升生成质量。 然而&#xff0c;目前关于 RLHF 的 scaling&#xff08;扩展&#xff09;潜力研究仍然…