企业员工信息中心管理系统(前后端从0到1开发—02)

一、详细设计——02

后端接口,业务逻辑开发

        在上一篇博客中(企业员工信息中心管理系统(前后端从0到1开发—01))。后端部分实现了对后端项目的初始化,并且创建了数据库和对应的表。使用TableGo自动生成基础代码模板,引用了mybatis-plus框架,并进行了单元测试。

        接下来则是根据详细设计中准备实现的功能,使用 mybatis-plus 的条件构造器和相关逻辑去实现对应的接口,并且完成前后端联调。

注册功能

        思路:根据用户账号,密码,校验密码,用户编号这四个参数校验(非空,用户账号不能小于6位且账号不能包含特殊字符,密码不能小于8位且密码和校验密码要相同),之后进行将这些数据插入到数据库中(密码加密加盐之后再进行存储)完成之后给前端返回一个注册成功之后的用户ID。

controller 层

    @PostMapping("/register")
    public BaseResponse<Long> userRegister (@NonNull @RequestBody UserRegisterRequest userRegisterRequest){

        String userAccount = userRegisterRequest.getUserAccount();
        String userPassword = userRegisterRequest.getUserPassword();
        String checkPassword = userRegisterRequest.getCheckPassword();
        String planetCode = userRegisterRequest.getPlanetCode();

        long register = userService.userRegister(userAccount, userPassword, checkPassword, planetCode);
        return ResultUtils.success(register);
    }

        当然这里还用到了一个IDEA插件  Auto Filling Java Call Arguments  还有 上一篇中使用到的GenerateAllSetter  在对象属性较多的时候,可以大幅提高编码的效率。

 同时,还使用了一个dto中间对象,对传入参数进行了封装,并且加入了基本的参数校验:

/**
 * 用户注册请求体
 */
@Data
public class UserRegisterRequest implements Serializable {

    private static final long serialVersionUID = 3191241716373120793L;

    /**
     * 用户账号
     */
    @NotBlank(message = "用户账号不能为空")
    @Size(min = 6, message = "用户账号不能少于6位")
    @Pattern(regexp = "[^`~!@#$%^&*()+=|{}':;,[\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]+",
            message = "用户账号不能包含特殊字符")
    private String userAccount;

    /**
     * 用户密码
     */
    @NotBlank(message = "用户密码不能为空")
    @Size(min = 8, message = "用户密码不能少于8位")
    private String userPassword;

    /**
     * 校验密码
     */
    @NotBlank(message = "校验密码不能为空")
    private String checkPassword;

    /**
     * 用户编号
     */
    @NotBlank(message = "用户编号不能为空")
    private String planetCode;
}

当然,我这里还提前配置了一下,自动导包,代码自动补全SpringBoot快捷键 以及常用包名解释_java规范代码格式快捷键

 service 层

        虽然在controller 层进行了参数的校验,但是在service 层还是有必要再校验一层。在service 层中主要实现了对密码的加密和数据的插入操作。

        需要说明的是,关于参数判空详细的说明可以看Java 判空的常见方法_java判空,这里使用的是   StringUtils.isAnyBlank  。对应的是需要导入  import org.apache.commons.lang3.StringUtils;  而一般的单个参数判空直接导入  import org.springframework.util.StringUtils;

 /**
   * 给密码加盐值,混淆密码
   */
   private static final String SALT = "salt";


 /**
     * 用户注册
     *
     * @param userAccount   用户账户
     * @param userPassword  用户密码
     * @param checkPassword 校验密码
     * @param planetCode    星球编号
     * @return 新用户 id
     */
    @Override
    public long userRegister(String userAccount, String userPassword, String checkPassword, String planetCode) {
        // 1. 校验
        if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword, planetCode)) {
            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 (planetCode.length() > 5) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户编号过长");
        }
        // 账户不能包含特殊字符
        String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]";
        Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
        if (matcher.find()) {
            return -1;
        }
        // 密码和校验密码相同
        if (!userPassword.equals(checkPassword)) {
            return -1;
        }
        // 账户不能重复
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userAccount", userAccount);
        long count = userMapper.selectCount(queryWrapper);
        if (count > 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复");
        }
        // 星球编号不能重复
        queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("planetCode", planetCode);
        count = userMapper.selectCount(queryWrapper);
        if (count > 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "编号重复");
        }
        // 2. 加密
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        // 3. 插入数据
        User user = new User();
        user.setUserAccount(userAccount);
        user.setUserPassword(encryptPassword);
        user.setPlanetCode(planetCode);
        boolean saveResult = this.save(user);
        if (!saveResult) {
            return -1;
        }
        return user.getId();
    }

登录功能

        思路:目前作为单体项目进行开发,有两种方式,一个是使用 Jwt 的方式进行登录,另外一个是直接进行登录,将请求 request 存放到session 中,具体原理参考从”登录“过程看Jwt和Token。依据实际情况(此项目业务不复杂,登录要求不高),因此选择第二种方式。参数校验方面,直接参考注册逻辑来写就行。需要注意的是,当登录成功后返回给前端数据时,需要给用户数据脱敏(密码等敏感数据,不能返回)

       我们使用request,用getsession拿到session,用 setAttribute 往session里设置一些值(比如用户信息) ,可已将 session 中的 attributes 当成是一个map,至于为什么这样说,可以Ctrl+B 点进去看一下源码 。

发现这个类,有一个获取属性的值。

 

 有get属性,那就还有set属性。

 点进set属性的实现方法(随便点击一个),

 发现这里面有一个put方法,知道了 attributes 是一个 map

 

controller 层

/**
     * 用户登录
     *
     * @param userLoginRequest
     * @param request
     * @return
     */
    @PostMapping("/login")
    public BaseResponse<User> userLogin(@RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request) {
        if (userLoginRequest == null) {
            return ResultUtils.error(ErrorCode.PARAMS_ERROR);
        }
        String userAccount = userLoginRequest.getUserAccount();
        String userPassword = userLoginRequest.getUserPassword();
        if (StringUtils.isAnyBlank(userAccount, userPassword)) {
            return ResultUtils.error(ErrorCode.PARAMS_ERROR);
        }
        User user = userService.userLogin(userAccount, userPassword, request);
        return ResultUtils.success(user);
    }

service 层

 /**
     * 用户登录
     *
     * @param userAccount  用户账户
     * @param userPassword 用户密码
     * @param request
     * @return 脱敏后的用户信息
     */
    @Override
    public User userLogin(String userAccount, String userPassword, HttpServletRequest request) {
        // 1. 校验
        if (StringUtils.isAnyBlank(userAccount, userPassword)) {
            return null;
        }
        if (userAccount.length() < 4) {
            return null;
        }
        if (userPassword.length() < 8) {
            return null;
        }
        // 账户不能包含特殊字符
        String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~!@#¥%……&*()——+|{}【】‘;:”“’。,、?]";
        Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
        if (matcher.find()) {
            return null;
        }
        // 2. 加密
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        // 查询用户是否存在
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userAccount", userAccount);
        queryWrapper.eq("userPassword", encryptPassword);
        User user = userMapper.selectOne(queryWrapper);
        // 用户不存在
        if (user == null) {
            log.info("user login failed, userAccount cannot match userPassword");
            return null;
        }
        // 3. 用户脱敏
        User safetyUser = getSafetyUser(user);
        // 4. 记录用户的登录态
        request.getSession().setAttribute(USER_LOGIN_STATE, safetyUser);
        return safetyUser;
    }

/**
     * 用户脱敏
     *
     * @param originUser
     * @return
     */
    @Override
    public User getSafetyUser(User originUser) {
        if (originUser == null) {
            return null;
        }
        User safetyUser = new User();
        safetyUser.setId(originUser.getId());
        safetyUser.setUsername(originUser.getUsername());
        safetyUser.setUserAccount(originUser.getUserAccount());
        safetyUser.setAvatarUrl(originUser.getAvatarUrl());
        safetyUser.setGender(originUser.getGender());
        safetyUser.setPhone(originUser.getPhone());
        safetyUser.setEmail(originUser.getEmail());
        safetyUser.setPlanetCode(originUser.getPlanetCode());
        safetyUser.setUserRole(originUser.getUserRole());
        safetyUser.setUserStatus(originUser.getUserStatus());
        safetyUser.setCreateTime(originUser.getCreateTime());
        return safetyUser;
    }

用户注销功能

思路:注销功能其实很简单(并不是直接把用户的数据给删除了)只是设置了一个用户常量类

/**
 * 用户常量
 */
public interface UserConstant {
    /**
     * 用户登录态键
     */
    String USER_LOGIN_STATE = "userLoginState";
    /**
     * 默认权限
     */
    int DEFAULT_ROLE = 0;
    /**
     * 管理员权限
     */
    int ADMIN_ROLE = 1;
}

 设置了几个常量,当要注销用户的时候,就把这个状态值给去除掉就行了。

controller 层

    /**
     * 用户注销
     *
     * @param request
     * @return
     */
    @PostMapping("/logout")
    public BaseResponse<Integer> userLogout(HttpServletRequest request) {
        if (request == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        int result = userService.userLogout(request);
        return ResultUtils.success(result);
    }

service 层

    /**
     * 用户注销
     *
     * @param request
     */
    @Override
    public int userLogout(HttpServletRequest request) {
        // 移除登录态
        request.getSession().removeAttribute(USER_LOGIN_STATE);
        return 1;
    }

获取当前用户功能

controller 层

    /**
     * 获取当前用户
     *
     * @param request
     * @return
     */
    @GetMapping("/current")
    public BaseResponse<User> getCurrentUser(HttpServletRequest request) {
        Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
        User currentUser = (User) userObj;
        if (currentUser == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN);
        }
        long userId = currentUser.getId();
        // TODO 校验用户是否合法
        User user = userService.getById(userId);
        User safetyUser = userService.getSafetyUser(user);
        return ResultUtils.success(safetyUser);
    }

根据姓名查找用户功能

controller 层

    @GetMapping("/search")
    public BaseResponse<List<User>> searchUsers(String username, HttpServletRequest request) {
        if (!isAdmin(request)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        if (StringUtils.isNotBlank(username)) {
            queryWrapper.like("username", username);
        }
        List<User> userList = userService.list(queryWrapper);
        List<User> list = userList.stream().map(user -> userService.getSafetyUser(user)).collect(Collectors.toList());
        return ResultUtils.success(list);
    }

根据ID删除用户功能

controller 层

    @PostMapping("/delete")
    public BaseResponse<Boolean> deleteUser(@RequestBody long id, HttpServletRequest request) {
        if (!isAdmin(request)) {
            throw new BusinessException(ErrorCode.NO_AUTH);
        }
        if (id <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        boolean b = userService.removeById(id);
        return ResultUtils.success(b);
    }

判断是否为管理员功能

controller 层

    /**
     * 是否为管理员
     *
     * @param request
     * @return
     */
    private boolean isAdmin(HttpServletRequest request) {
        // 仅管理员可查询
        Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
        User user = (User) userObj;
        return user != null && user.getUserRole() == ADMIN_ROLE;
    }

后端接口测试

        这里使用postman 或者 apifox 或则 swagger 都行,不过 idea 中自带的api 调试工具也不错。下面对登录接口进行测试演示。

这是接口对应的返回值。 

二、需要说明的地方

        三层结构,并不是绝对的三层,只是按照实际情况下,对程序进行解耦和简化,但是如果代码逻辑过于简单,几行代码就写完了也不用特意分成三层结构。像使用了mybatis-plus之后,CRUD功能全部集成好了,直接传参就行了,所以说后面几个接口直接在controller层就写完了。

猜你喜欢

转载自blog.csdn.net/weixin_49171365/article/details/131818169