人力资源项目学习

特点

特点1:对象转化为树形结构

好处

将对象转化为树形结构(例如:菜单、权限等)有许多实际的好处,特别是在处理具有层级关系的数据时。通过您的代码实现的树形结构转换,可以带来以下几个显著优势:

1. 直观的层级关系展示
  • 树形结构可以清晰地展示数据之间的层级关系,例如菜单或权限等。通过树形结构,可以清楚地看到每个节点的父子关系,帮助开发者和用户更容易理解数据的组织方式。例如,在一个菜单系统中,根菜单项和子菜单项之间的层级关系就能直观地通过树状图表现出来。
2. 数据操作更便捷
  • 树形数据结构使得对层次数据的操作变得更简单。你可以通过递归查找父节点或子节点、更新层级、删除节点等操作,显著提高了开发效率。
  • 比如,你可以很容易地获取到某个角色的所有权限项,以及它们的层级关系,或者从一个父节点开始查找所有子节点并做递归操作。
3. 提高查询效率
  • 树形结构帮助快速获取和操作数据的层级。例如,在权限管理系统中,如果需要根据角色来获取相关权限的层级,使用树形结构可以让你非常方便地遍历这些数据,避免了频繁的多次查询。
4. 灵活的数据展示
  • 转换为树形结构后,你可以根据需要对数据进行可视化展示。例如,在前端应用中,可以将这些数据渲染为树形菜单、组织结构等,用户界面变得更加直观易用。
  • 对于复杂的数据展示,树形结构能够清晰地呈现多层次的结构,便于用户理解和操作。
5. 便于扩展和维护
  • 使用树形结构可以更方便地扩展和维护应用的功能。假如未来需要添加新的层级或字段,树形结构的实现可以灵活地应对这些需求。你只需要调整树节点的属性和方法,而不需要大规模地重构数据结构。
  • 树形结构提供了层次化的组织方式,便于以后根据需求添加、删除、修改节点等,增强了代码的可维护性和可扩展性。
6. 支持多种树操作
  • 树形数据结构可以支持各种常见的树操作,例如:
    • 深度优先搜索(DFS):递归遍历子节点,可以方便地实现层级数据的遍历。
    • 广度优先搜索(BFS):可以通过层级顺序来遍历节点,适用于不同的需求。
    • 删除/添加节点:可以非常容易地进行节点的添加和删除操作,并确保树的层级结构仍然正确。
    • 节点重排:如果需要调整节点的顺序或结构,可以直接操作树结构。
7. 解耦合,灵活性高
  • 通过使用TreeNodeMapper接口,数据的转换(如Right转为RightTreeVO)与树形数据的构建分离,增强了系统的解耦性。不同类型的数据可以通过实现不同的TreeNodeMapper来实现灵活的树形结构转换,使得系统更具灵活性和扩展性。
8. 更好的权限控制和安全性
  • 如果你的应用涉及权限控制,树形结构特别适用于权限的分配和管理。你可以通过树形结构高效地管理不同角色和用户的权限层级。通过树形结构,可以清晰地知道哪些权限是父权限,哪些是子权限,以及如何进行继承和控制访问。
9. 数据归类和分组
  • 树形结构可以帮助你将数据进行归类和分组。例如,菜单系统可以通过树形结构将菜单项按层级组织,这样不仅可以提升系统的可操作性,还能便于权限和用户管理的控制。
10. 可扩展性
  • 如果将来需求发生变化,例如需要支持动态增加/删除节点、支持更多层级、或者增加其他属性,树形结构使得你可以方便地扩展当前的数据模型,保证系统的灵活性。
总结:

将对象转成树形结构主要的好处是:可视化层级关系便捷的数据操作高效的查询和修改灵活性和可扩展性,同时还能帮助系统实现更高效的权限管理、数据展示和操作。通过树形结构的设计,系统的维护和扩展将变得更加简洁清晰,有助于提升开发效率与用户体验。

实现从对象转变为树的节点

1  首先先定义节点对象TreeNode 
package com.zeroone.star.project.utils.tree;

import lombok.Setter;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 * 描述:用来定义一个树形节点的数据
 * </p>
 * <p>版权:&copy;01星球</p>
 * <p>地址:01星球总部</p>
 * @author 阿伟学长
 * @version 1.0.0
 */
public class TreeNode implements Serializable {
    /**
     * 唯一ID
     */
    @Setter
    protected String tnId;
    /**
     * 节点父节点ID
     */
    @Setter
    protected String tnPid;
    /**
     * 节点深度
     */
    @Setter
    protected Integer tnDepth;
    /**
     * 节点包含的子节点
     */
    protected List<TreeNode> tnChildren;

    /**
     * 添加子节点,如果子类需要管理添加子节点操作可以通过重写此函数实现
     * @param child 子节点对象
     */
    public void addChild(TreeNode child) {
        // 判断子节点集合是否为空
        if (tnChildren == null) {
            tnChildren = new ArrayList<>();
        }
        // 添加子节点
        tnChildren.add(child);
    }

    /**
     * children类型转换
     * @return 返回转换后的集合
     * @param <T> 转换到的类型
     */
    protected <T> List<T> childrenElementTrans() {
        // 非空验证
        if (tnChildren == null || tnChildren.isEmpty()) {
            return null;
        }
        return tnChildren.stream().map(one -> (T) one).collect(Collectors.toList());
    }
}

分析:

属性

tnId(节点ID):每个节点的唯一标识符,String 类型。用于唯一标识树中的一个节点。

tnPid(父节点ID):记录该节点的父节点ID。通过父节点ID,可以确定树的层次关系。

tnDepth(节点深度):该节点在树中的深度,也即该节点距离根节点的层级。通过这个属性,节点的层次结构更加明确。

tnChildren(子节点列表):List<TreeNode> 类型的属性,存储该节点的所有子节点。每个节点可能包含多个子节点,形成一个多叉树的结构。

构造方法和功能

addChild 方法:此方法用于向当前节点添加子节点。它首先检查 tnChildren 是否为 null,如果是,则初始化为一个新的 ArrayList<TreeNode>。然后将子节点 child 添加到 tnChildren 列表中。该方法是树形结构操作中的核心方法之一,它实现了父节点与子节点之间的关联。 childrenElementTrans 方法:该方法将当前节点的子节点列表 tnChildren 转换为一个泛型类型的列表 List<T>,它的返回类型可以是任何类型 T,并且通过 Stream API 将每个子节点进行类型转换。 该方法首先检查 tnChildren 是否为空或为 null,如果是,则返回 null。 然后通过 Stream 流操作,将 tnChildren 中的每个元素(类型为 TreeNode)转换为类型 T。

2  定义一个mapper-TreeNodeMapper将其他对象转化为节点对象

定义一个接口,用于后续去实现这一个接口,来定义具体对象转化为节点的具体方法

package com.zeroone.star.project.utils.tree;

/**
 * <p>
 * 描述:对象转换节点数据匹配接口
 * </p>
 * <p>版权:&copy;01星球</p>
 * <p>地址:01星球总部</p>
 *
 * @author 阿伟学长
 * @version 1.0.0
 */
public interface TreeNodeMapper<T> {

    /**
     * 把一个对象转换为节点的数据对象
     * @param object 被转换对象
     * @return 转换后的节点数据
     */
    TreeNode objectMapper(T object);
}

3  定义一个树形数据构建工具类TreeUtils 

里面封装了将把集合中的数据转换为树形节点的方法

package com.zeroone.star.project.utils.tree;

import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * 描述:树形数据构建工具类
 * </p>
 * <p>版权:&copy;01星球</p>
 * <p>地址:01星球总部</p>
 * @author 阿伟学长
 * @version 1.0.0
 */
public class TreeUtils {

    /**
     * 将列表数据转换成树状数据
     * @param list   列表数据
     * @param mapper 数据转换节点数据匹配接口
     * @param <S>    输入数据类型一般是DO
     * @param <T>    结果数据类型一般是DTO
     * @return 返回树状数据列表
     */
    public static <S, T> List<T> listToTree(List<S> list, TreeNodeMapper<S> mapper) {
        // 1 把集合中的数据转换为树形节点数据
        List<TreeNode> nodes = new ArrayList<>();
        for (S row : list) {
            TreeNode node = mapper.objectMapper(row);
            nodes.add(node);
        }
        // 2 构建一个具有层次结构的树
        List<T> tree = new ArrayList<>();
        // 3 循环获取根节点
        for (TreeNode node : nodes) {
            if (null == node.tnPid) {
                node.setTnDepth(0);
                tree.add((T) node);
                // 查找子节点
                findChildNodes(node, nodes);
            }
        }
        return tree;
    }

    /**
     * 查找并设置指定父节点的对应子节点
     * @param parentNode 父节点
     * @param nodes      节点集合
     */
    private static void findChildNodes(TreeNode parentNode, List<TreeNode> nodes) {
        for (TreeNode child : nodes) {
            // 找到子节点
            if (parentNode.tnId.equals(child.tnPid)) {
                // 设置子节点的相关层次数据
                child.setTnDepth(parentNode.tnDepth + 1);
                // 将子节点添加到父节点的子节点集合中
                parentNode.addChild(child);
                // 查找子节点包含的子节点
                findChildNodes(child, nodes);
            }
        }
    }
}

listToTree 方法的工作流程:

参数说明:

list:传入的原始数据列表,通常是从数据库或其他地方获取的一个扁平化列表。这些数据对象具有父子关系,父节点的标识符通常存储在 tnPid 字段中,子节点通过父节点ID关联。

mapper:一个实现了 TreeNodeMapper<S> 接口的对象,用于将输入数据(S)转换为树形节点(TreeNode)。通过这个接口,方法将原始数据对象映射为树形节点对象,并获取树的相关信息(如ID、父节点ID、子节点等)。

数据转换为树形节点:

该方法首先通过 mapper.objectMapper(row) 将输入的每一个数据对象 row 映射为一个 TreeNode 对象,并将这些树形节点存储到 nodes 列表中。

此时,所有的数据元素已经被转化为树的节点,但它们仍然是一个扁平化的结构,没有体现父子关系。

构建树形结构:

接着,方法创建一个空的 tree 列表,用于存储根节点。

遍历所有的树节点,如果节点的父节点ID (tnPid) 为 null,说明该节点是根节点(没有父节点)。此时,将根节点的深度 (tnDepth) 设置为 0,并将其添加到 tree 列表中。

递归查找并设置子节点:

对于每个根节点,调用 findChildNodes 方法来查找并设置它的子节点。

findChildNodes 方法会遍历所有节点,查找那些父节点ID与当前节点的ID匹配的节点,将这些节点作为当前节点的子节点。

每找到一个子节点,方法会递归地调用 findChildNodes,以确保所有层级的子节点都被正确设置。这一递归过程确保了树形结构的完整性,节点间的父子关系被逐步构建。

返回树形数据: 最终,listToTree 方法返回构建好的树形结构(List<T>),即包含所有根节点及其子节点的树状数据。

4  对mapper的实现-RightTreeNodMapper 

我们以查询节点的业务逻辑为例子。这里就定义了将权限对象转化为树节点的具体mapper

/**
 * The implementation of {@link RightService}, base on {@link ServiceImpl}
 *
 * @author authoralankay
 * @see RightService
 * @see ServiceImpl
 */

class RightTreeNodMapper implements TreeNodeMapper<Right> {
    @Override
    public TreeNode objectMapper(Right right) {
        RightTreeVO treeNode = new RightTreeVO();
        // 首先设置TreeNode计算层数使用属性
        treeNode.setTnId(right.getId());
        if (right.getParentRightId() == null) {
            treeNode.setTnPid(null);
        } else {
            treeNode.setTnPid(right.getParentRightId());
        }
        // 设置扩展属性
        treeNode.setId(right.getId());
        treeNode.setName(right.getName());
        treeNode.setLinkUrl(right.getLinkUrl());
        treeNode.setPid(right.getParentRightId());
        return treeNode;
    }
}

分析:

RightTreeNodMapper 类实现了 TreeNodeMapper<Right> 接口,

方法 objectMapper 接受一个 Right 对象

首先根据 Right 对象的 idparentRightId 设置树形节点的 ID (tnId) 和父节点 ID (tnPid)。

如果 parentRightIdnull,则父节点 ID 也设为 null,表示该节点是根节点。

接着,它将 Right 对象的其他属性(如 namelinkUrlparentRightId)复制到树形节点的扩展属性中(如 idnamelinkUrlpid)。

最终,该方法返回构建好的 RightTreeVO 对象,该对象作为树形结构中的一个节点,方便后续的树形数据构建和层级关系的处理。

上面的操作就是实现最终返回转换后的树形数据的大部分逻辑。其中RightTreeVO 是继承treeNode的,这里涉及到具体的业务逻辑,与实现树结构无关,所以不多赘述。

一  登陆模块业务

1  登陆(Redis)

在hr-login微服务,入口在loginController的authLogin方法

相应代码:

@ApiOperation(value = "授权登录")
    @PostMapping("auth-login")
    @Override
    public JsonVO<Oauth2TokenDTO> authLogin(LoginDTO loginDTO) {
        //1.验证码验证
        if (openCaptcha) {
            CaptchaVO captchaVO = new CaptchaVO();
            captchaVO.setCaptchaVerification(loginDTO.getCode());
            ResponseModel response = captchaService.verification(captchaVO);
            if (!response.isSuccess()) {
                JsonVO<Oauth2TokenDTO> fail = fail(null);
                fail.setMessage(response.getRepCode() + response.getRepMsg());
                //验证码校验失败,返回信息告诉前端
                //repCode  0000  无异常,代表成功
                //repCode  9999  服务器内部异常
                //repCode  0011  参数不能为空
                //repCode  6110  验证码已失效,请重新获取
                //repCode  6111  验证失败
                //repCode  6112  获取验证码失败,请联系管理员
                return fail;
            }
        }
        //2.账号密码认证
        Map<String, String> params = new HashMap<>(5);
        params.put("grant_type", "password");
        params.put("client_id", loginDTO.getClientId());
        params.put("client_secret", AuthConstant.CLIENT_PASSWORD);
        params.put("username", loginDTO.getUsername());
        params.put("password", loginDTO.getPassword());
        //3.调用远程接口,获取Token
        JsonVO<Oauth2TokenDTO> oauth2TokenDTO = oAuthService.postAccessToken(params);
        //4.将授权token存储到Redis中,记录登录状态

        if (oauth2TokenDTO.getData() == null) {
            System.out.println("******oauth2TokenDTO为空!********");
            return fail(null, ResultStatus.SERVER_ERROR);
        }

        String token = oauth2TokenDTO.getData().getToken();
        //4.1拼接key
        String userTokenKey = RedisConstant.USER_TOKEN + ":" + token;
        //4.2逻辑判断
        if (redisUtils.add(userTokenKey, 1, 1L, TimeUnit.HOURS) < 0) {
            return fail(oauth2TokenDTO.getData(), ResultStatus.SERVER_ERROR);
        }
        //返回结果token
        return oauth2TokenDTO;
    }

流程

  1. 验证码验证:首先,代码会检查是否启用验证码(openCaptcha),如果启用,调用验证码服务进行验证。如果验证码验证失败,会返回包含错误信息的响应对象(JsonVO<Oauth2TokenDTO>)。验证码服务通过captchaService.verification()方法进行验证,该方法返回的ResponseModel包含repCoderepMsg,用于描述验证结果。

  2. 账号密码认证:如果验证码验证通过,接下来将用户的账号(username)和密码(password)等信息封装成一个Map,并使用这些信息通过OAuth服务(oAuthService.postAccessToken())向远程接口请求授权Token。该请求的参数包括:

    • grant_type: 表示授权类型,值为password表示密码授权。
    • client_idclient_secret: 客户端ID和客户端密码。
    • usernamepassword: 用户的登录凭证。
  3. 获取授权Token:远程接口返回的JsonVO<Oauth2TokenDTO>对象包含了授权Token数据。如果Token为空,则表示获取失败,返回一个带错误码的失败响应。Oauth2TokenDTO是一个数据传输对象(DTO),用于封装获取的OAuth2 Token。

  4. 存储Token到Redis:如果获取到Token,代码将通过Redis存储Token,以便后续验证用户的登录状态。使用Redis的add方法将用户的Token存入,设置过期时间为1小时。存储的键值是userTokenKey,形式为RedisConstant.USER_TOKEN:token,确保每个Token都有唯一的存储位置。

  5. 返回结果:最后,成功获取Token后,返回包含Token数据的JsonVO<Oauth2TokenDTO>对象。如果发生错误,则返回一个失败的JsonVO对象。

使用的框架和技术

  1. Spring Boot:该代码使用了Spring Boot框架来构建RESTful API接口。@PostMapping用于定义HTTP POST请求的路由,@ApiOperation是Swagger的注解,用于描述API接口。

  2. OAuth2:代码中实现了OAuth2的授权认证流程。通过密码模式(password grant type)进行用户身份验证,获取OAuth2授权Token。相关服务通过oAuthService.postAccessToken()方法实现与OAuth2服务的交互。

  3. Redis:用于存储和管理用户登录的Token。代码中使用redisUtils.add()方法将Token存入Redis,并设置了一个过期时间,确保Token在一定时间内有效。

  4. 验证码服务:在启用验证码验证时,调用了captchaService.verification()来验证用户输入的验证码。验证码服务返回ResponseModel,用来表示验证结果。

  5. 自定义对象

    • JsonVO<Oauth2TokenDTO>:封装API响应数据的对象,包含状态码、消息和数据。
    • Oauth2TokenDTO:包含OAuth2授权Token的DTO对象。
    • CaptchaVO:用于封装验证码验证请求的数据对象。
    • ResponseModel:封装验证码验证结果的数据对象。

相应业务流程路径

E:\简历项目\零壹人力资源管理系统(01hrsys)\02、项目需求\业务流程文档\细化业务流程\j1\登录系统

2  修改用户

在hr-j1-sysmanager下的UserController的modifyUser方法

详细代码:

Controller:

@ApiOperation(value = "修改用户")
    @PutMapping("modify")
    @Override
    public JsonVO<Boolean> modifyUser(@Validated UserDTO dto) {
        return userService.updateUser(dto) ? JsonVO.success(true) : JsonVO.fail(false);
    }

Service:

@Override
    public Boolean updateUser(UserDTO dto) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getId, dto.getId());
        User user = new User();
        BeanUtil.copyProperties(dto, user);
        LocalDateTime now = LocalDateTime.now();
        user.setUpdateTime(now);
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        user.setPassword(passwordEncoder.encode(dto.getPassword()));
        return baseMapper.update(user, wrapper) >= 1;
    }

1. modifyUser 方法(Controller 层)

接收前端的 UserDTO 数据,经过验证后调用 userService.updateUser(dto) 更新用户信息。

根据 updateUser 的返回值,返回成功或失败的 JsonVO 响应。

2. updateUser 方法(Service 层)

使用 LambdaQueryWrapper 根据用户 ID 查找目标用户。

将 UserDTO 中的属性值拷贝到 User 实体中。

设置当前时间为更新时间,

使用 BCryptPasswordEncoder 加密新密码。

使用 MyBatis-Plus 的 baseMapper.update 执行更新操作,返回是否成功更新(影响行数 ≥ 1)。

用到的框架:

Spring Web:处理 HTTP 请求。

Swagger:生成 API 文档。

Spring Validation:验证传入数据。

MyBatis-Plus:简化数据库操作。

Hutool:属性拷贝工具。

Spring Security:密码加密工具。

3  退出登陆(Redis)

代码:

@ApiOperation(value = "退出登录")
    @GetMapping("logout")
    @Override
    public JsonVO<String> logout() throws Exception {
        //1.判断缓存中是否存在对应token
        String tokenKeyInRedis = RedisConstant.USER_TOKEN + ":" + userHolder.getCurrentUserToken();
        if (!redisUtils.isExist(tokenKeyInRedis)) {
            //不存在
            return fail(null, ResultStatus.UNAUTHORIZED);
        }
        //存在
        //登出逻辑,需要配合登录逻辑实现
        //1.获取当前用户token
        String currentUserToken = userHolder.getCurrentUserToken();
        //2.拼接key
        String userTokenKey = RedisConstant.USER_TOKEN + ":" + currentUserToken;
        //2.删除当前用户token
        int del = redisUtils.del(userTokenKey);
        if (del < 0) {
            return JsonVO.fail("退出失败!");
        }
        return JsonVO.success("退出成功!");
    }

1. logout 方法(Controller 层) 业务流程

检查 Redis 中是否存在当前用户的 Token:

 构建一个 Redis 键:tokenKeyInRedis = RedisConstant.USER_TOKEN + ":" + userHolder.getCurrentUserToken(),该键用于存储当前用户的 Token 信息。

使用 redisUtils.isExist(tokenKeyInRedis) 检查 Redis 中是否存在该 Token。 如果 Token 不存在,表示用户尚未登录或 Token 已失效,返回一个 fail 响应,提示未授权。

执行用户登出操作:

获取当前用户的 Token:currentUserToken = userHolder.getCurrentUserToken()。

拼接 Redis 键:userTokenKey = RedisConstant.USER_TOKEN + ":" + currentUserToken,该键用于标识当前用户的登录状态。

调用 redisUtils.del(userTokenKey) 删除 Redis 中存储的 Token。 如果删除失败(返回值小于 0),返回一个失败的响应,表示退出失败。 如果删除成功,返回一个成功的响应,表示退出成功。

2. 使用的框架和工具:

Spring Web:用于处理 HTTP 请求,@GetMapping 用于处理 GET 请求。

Swagger:@ApiOperation 用于生成 API 文档,描述接口的功能为“退出登录”。

Redis:用于存储和验证用户的 Token,redisUtils 是封装了 Redis 操作的工具类。 自定义响应类 (JsonVO):JsonVO 是一个自定义的响应封装类,用于统一返回格式,包括成功 (JsonVO.success()) 或失败 (JsonVO.fail())。

自定义常量类 (RedisConstant):存储 Redis 键的常量类,USER_TOKEN 表示存储用户 Token 的 Redis 键前缀。

userHolder:持有当前用户信息的类,getCurrentUserToken() 获取当前用户的 Token。

4  获取用户信息(Redis)

代码:

@ApiOperation(value = "获取当前用户")
    @GetMapping("current-user")
    @Override
    public JsonVO<LoginVO> getCurrUser() {
        //1.判断缓存中是否存在对应token
        String tokenKeyInRedis = RedisConstant.USER_TOKEN + ":" + userHolder.getCurrentUserToken();
        if (!redisUtils.isExist(tokenKeyInRedis)) {
            //不存在
            return fail(null, ResultStatus.UNAUTHORIZED);
        }
        //存在
        //UserDTO 用户id,用户名称,是否启用,用户拥有角色列表
        UserDTO currentUser;
        try {
            currentUser = userHolder.getCurrentUser();
        } catch (Exception e) {
            return JsonVO.create(null, ResultStatus.FAIL.getCode(), e.getMessage());
        }
        if (currentUser == null) {
            return fail(null);
        } else {
            //需要(LoginVo):1.用户id,2.用户名,3.是否启用(1启用0禁用),4.用户角色列表
            User user = userService.getById(currentUser.getId());
            //设置用户id
            currentUser.setId(user.getId());
            //设置用户名
            currentUser.setUsername(user.getUsername());
//            currentUser.setIsEnabled((byte) user.getIsEnable());
            //设置用户角色列表
            List<Role> roleList = roleService.listRoleByUserId(user.getId());
            List<String> roleStringList = new ArrayList<>();
            //转换
            for (Role role : roleList) {
                roleStringList.add(role.getName());
            }
            currentUser.setRoles(roleStringList);
            //这里需要根据业务逻辑接口,重新实现
            LoginVO vo = new LoginVO();
            BeanUtil.copyProperties(currentUser, vo);
            return JsonVO.success(vo);
        }
    }

流程:

这段代码实现了获取当前用户信息的接口。

首先,它检查 Redis 中是否存在当前用户的 Token,若不存在则返回未授权错误;

若 Token 存在,则通过 userHolder 获取当前用户信息。

如果获取失败(例如抛出异常或用户信息为空),则返回失败响应。

如果成功获取到用户信息,接着通过用户 ID 获取用户的详细信息和角色列表,并将这些信息封装成 Log

二  权限模块业务

1  修改权限

代码:

@ApiOperation(value = "修改权限")
    @PostMapping("/modify-right")
    @Override
    public JsonVO<Boolean> modifyRight(RightDTO dto) {
        return rightService.modifyRight(dto);
    }
@Override
    public JsonVO<Boolean> modifyRight(RightDTO dto) {
        String id = dto.getId();
        Right right = getById(id);
        if (Objects.isNull(right)) {
            return JsonVO.create(false, ResultStatus.PARAMS_INVALID.getCode(), "权限不存在");
        }
        // 判断 parentRightId 是否存在
        String parentRightId = dto.getParentRightId();
        boolean parentRightIdNotExist = Objects.isNull(getById(parentRightId));
        if (parentRightIdNotExist) {
            return JsonVO.create(false, ResultStatus.PARAMS_INVALID.getCode(), "parent right id 不存在");
        }
        // 将 dto 的非空属性赋值给 right 权限,然后更新权限
        BeanUtil.copyProperties(dto, right, CopyOptions.create().setIgnoreNullValue(true));
        right.setUpdateTime(new Date(System.currentTimeMillis()));
        boolean result = updateById(right);
        if (result) {
            return JsonVO.success(true);
        }
        return JsonVO.fail(false);
    }

1. modifyRight 方法(Controller 层) 功能:

接收前端请求的数据,调用 rightService.modifyRight(dto) 来修改权限信息。

业务:根据请求的权限数据调用服务层处理逻辑。

2. modifyRight 方法(Service 层)

验证权限:检查权限 ID 是否存在,若不存在返回失败。

验证上级权限:检查 parentRightId 是否有效,若无效返回失败。

更新权限:将前端传来的数据(非空属性)复制到数据库权限对象,并设置更新时间,最后更新数据库中的记录。

返回结果:如果更新成功,返回成功响应;否则返回失败响应。

使用的框架:

Spring Web:处理 HTTP 请求。

Swagger:生成 API 文档。

MyBatis-Plus:数据库操作。

Hutool:属性拷贝工具。

自定义响应类 (JsonVO):统一的响应格式。

2  增加权限(雪花算法)

代码:

@ApiOperation(value = "增加权限")
    @PostMapping("/add-right")
    @Override
    public JsonVO<Boolean> addRight(RightDTO dto) {
        return rightService.addRight(dto);
    }
@Override
    public JsonVO<Boolean> addRight(RightDTO dto) {
        // 判断 parentRightId 是否存在
        String parentRightId = dto.getParentRightId();
        boolean parentRightIdNotExist = Objects.isNull(getById(parentRightId));
        if (parentRightIdNotExist) {
            return JsonVO.create(false, ResultStatus.PARAMS_INVALID.getCode(), "parent right id 不存在");
        }
        // 将 dto 的非空属性赋值给 right 权限,然后添加权限
        Right right = new Right();
        BeanUtil.copyProperties(dto, right, CopyOptions.create().setIgnoreNullValue(true));
        right.setId(String.valueOf(snowflake.nextId()));
        try {
            right.setCreator(userHolder.getCurrentUser().getUsername());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        Date date = new Date(System.currentTimeMillis());
        right.setCreateTime(date);
        right.setUpdateTime(date);
        boolean result = save(right);
        if (result) {
            return JsonVO.success(true);
        }
        return JsonVO.fail(false);
    }

这段代码的核心功能是“增加权限”,其主要流程和使用的框架、工具可以概括如下:

流程概括

  1. 调用业务层方法

    • rightService.addRight(dto):接收来自客户端的权限数据 RightDTO,然后将该数据传递给业务层的 addRight 方法进行处理,返回一个包含操作结果的 JsonVO<Boolean> 对象。
  2. 父权限 ID 校验

    • RightDTO 获取 parentRightId,用于确定新权限的父权限。
    • 调用 getById(parentRightId) 查询数据库或权限系统,检查父权限是否存在。
    • 如果父权限不存在(即查询结果为 null),返回一个包含错误信息的 JsonVO 响应。
  3. DTO 转换为实体对象

    • 使用 Hutool 库的 BeanUtil.copyProperties()RightDTO 中的非空属性复制到新的 Right 实体对象中,确保不会覆盖已有的数据。
    • 使用 Snowflake ID 生成器 生成唯一的权限 ID,确保每个权限都有一个全局唯一标识。
  4. 设置权限的创建者和时间戳

    • 使用 userHolder 获取当前登录用户的信息,并将其设置为新权限的创建者。
    • 设置当前时间为权限的创建时间和更新时间。
  5. 保存权限

    • 调用 save(right) 方法保存新创建的权限对象到数据库中,通常通过 JPA 或 MyBatis 实现数据库持久化。
  6. 返回操作结果

    • 如果权限保存成功,返回一个表示成功的 JsonVO 对象,携带 true 值。
    • 如果保存失败,返回一个表示失败的 JsonVO 对象,携带 false 值。

使用的框架和工具

  1. Spring Boot

    • 用于构建 RESTful API,使用了 @PostMapping 注解来处理 HTTP POST 请求,@Override 用于重写接口方法。
    • JsonVO 用于封装 API 响应,统一返回格式。
  2. Swagger

    • @ApiOperation 注解来自 Swagger,用于生成 API 文档,帮助开发者理解接口功能。
  3. Hutool

    • BeanUtil.copyProperties() 用于将 RightDTO 对象的数据转换为 Right 实体。Hutool 提供了简洁的工具方法,简化了对象属性的复制操作。
  4. Snowflake ID 生成器:

    • snowflake.nextId() 用于生成全局唯一的 ID。常用于分布式系统中,确保在多个服务之间生成唯一的标识符。
  5. 自定义工具类(如 userHolder

    • userHolder 是一个用于获取当前用户信息的工具类,它在这段代码中用来获取当前登录用户的用户名,确保记录权限创建者。

3  删除权限

代码:

controller

@ApiOperation(value = "删除权限")
    @DeleteMapping("/remove-right")
    @Override
    public JsonVO<Boolean> removeRight(@RequestParam String id) {
        return rightService.removeRight(id);
    }

service

@Override
    public JsonVO<Boolean> removeRight(String id) {
        if (RIGHT_ROOT_ID.equals(id)) {
            // 不能删除根权限
            return JsonVO.create(false, ResultStatus.FORBIDDEN.getCode(), "不能删除根权限");
        }
        Right right = getById(id);
        if (Objects.isNull(right)) {
            return JsonVO.create(false, ResultStatus.PARAMS_INVALID.getCode(), "权限不存在");
        }
        boolean result = removeById(right);

        if (result) {
            return JsonVO.success(true);
        }
        return JsonVO.fail(false);
    }

逻辑:

这段代码实现了删除权限的功能。

首先,它检查是否尝试删除根权限(通过 RIGHT_ROOT_ID 判断),如果是,则返回错误提示,禁止删除。

接着,通过 getById(id) 方法验证权限是否存在,若不存在则返回“权限不存在”的错误。

如果权限存在,则调用 removeById(right) 执行删除操作。

如果删除成功,返回 JsonVO.success(true) 表示操作成功;

否则,返回 JsonVO.fail(false) 表示删除失败。

4  查询权限(树结构节点)

代码:

@ApiOperation(value = "查询权限结构树")
    @GetMapping("/query-right-tree")
    @Override
    public JsonVO<List<RightTreeVO>>queryRightTree() {
        //1 获取当前用户
        UserDTO currentUser = null;
        try {
            currentUser = userHolder.getCurrentUser();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        //2 获取当前用户拥有的权限
        List<RightTreeVO> rights = rightService.listRightByRoleName(currentUser.getRoles());
        return JsonVO.success(rights);
    }
@Override
    public List<RightTreeVO> listRightByRoleName(List<String> roleNames) {
        //1 定义一个存储数据库查询菜单数据的容器
        List<Right> rights = new ArrayList<>();
        //2 遍历获取角色获取所有的菜单列表
        roleNames.forEach(roleName -> {
            //通过角色名获取菜单列表
            List<Right> tRights = baseMapper.selectByRoleName(roleName);
            if (tRights != null && !tRights.isEmpty()) {
                rights.addAll(tRights);
            }
        });
        //3 转换树形结构并返回
        return TreeUtils.listToTree(rights, new RightTreeNodMapper());
    }

这段代码实现了查询当前用户的权限结构树的功能,主要包括两个部分:查询用户权限和转换权限数据为树形结构。

  1. 查询当前用户权限:在queryRightTree()方法中,通过userHolder.getCurrentUser()获取当前用户的详细信息(如角色)。如果获取过程中发生异常,会抛出运行时异常。接着,通过调用rightService.listRightByRoleName()方法传入当前用户的角色名称来获取用户对应的权限列表。

  2. 查询角色对应权限列表:在listRightByRoleName()方法中,遍历当前用户的所有角色,针对每个角色,通过baseMapper.selectByRoleName(roleName)查询该角色对应的权限数据。将所有角色的权限数据汇总到rights列表中。

  3. 转换为树形结构:汇总的权限数据会通过TreeUtils.listToTree(rights, new RightTreeNodMapper())方法转换成树形结构,其中RightTreeNodMapper是用来定义树结构节点的转换规则。最终返回转换后的树形数据。(这里的详细解析在上面特点的特点一,讲述如何最终返回转换后的树形数据。)

总结来说,代码逻辑是先通过用户角色获取所有权限,然后将权限数据转换为树形结构返回给前端,用于展示权限树。

转为树的过程,看上面的特点1

还有模糊查询和分页查询

三  角色模块业务

1  新增角色

代码实现:

@ApiOperation(value = "新增角色")
    @PostMapping("/add-one")
    @Override
    public JsonVO<Boolean> addOneRole(RoleDTO dto) {
        if (dto.getDescription() == null &&
                dto.getName() == null &&
                dto.getKeyword() == null) {
            return JsonVO.fail(false);
        }
        Boolean addResult = roleService.addRole(dto);
        if (addResult) {
            return JsonVO.success(true);
        } else {
            return JsonVO.fail(false);
        }
    }

@Override
    public Boolean addRole(RoleDTO roleDTO) {
        Role role = new Role();
        role.setId(String.valueOf(snowflake.nextId()));
        role.setName(roleDTO.getName());
        role.setKeyword(roleDTO.getKeyword());
        role.setDescription(roleDTO.getDescription());
        role.setIsEnable(roleDTO.getIsEnable());
        try {
            role.setCreator(userHolder.getCurrentUser().getUsername());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        Date date = new Date(System.currentTimeMillis());
        role.setCreateTime(date);
        role.setUpdateTime(date);
        //角色创建者
        role.setCreator(roleDTO.getCreator());
        int result = baseMapper.insert(role);
        return result == 1;
    }

流程:

首先,addOneRole 方法接收一个 RoleDTO 对象并检查其中的 descriptionnamekeyword 是否为空,如果都为空则返回失败。

接着,调用 roleService.addRole(dto) 方法将角色数据保存到数据库中。

如果保存成功,返回成功的响应;否则返回失败的响应。

addRole 方法负责实际的角色保存操作,它将 RoleDTO 转换为 Role 实体,设置角色的相关属性(如名称、描述、创建者等),然后调用数据库插入操作。如果插入成功,返回 true,否则返回 false

2  删除角色

前端传入要删除角色的id,然后鉴权判断当前角色是否有删除角色的权利,如果有就根据id删除角色

@ApiOperation(value = "删除角色")
    @DeleteMapping("/delete")
    @Override
    public JsonVO<Boolean> deleteRole(@ApiParam(value = "需删除的角色id", example = "2") @RequestParam String id) {
        boolean result = roleService.deleRoleById(id);
        if (result) {
            return JsonVO.success(true);
        } else {
            return JsonVO.fail(false);
        }
    }
@Override
    public Boolean deleRoleById(String id) {
        int result = baseMapper.deleteById(id);
        return result == 1;
    }

3  修改角色

代码:

@ApiOperation(value = "修改角色")
    @PostMapping("/modify")
    @Override
    public JsonVO<Boolean> modifyRole(RoleDTO dto) {
        //输入的id不能为空
        if (dto.getId() == null) {
            JsonVO.fail(false);
        }
        //角色描述字数限制
        if (dto.getDescription().length() > 50) {
            JsonVO.fail(false);
        }
        Boolean modifyResult = roleService.modifyRole(dto);
        if (modifyResult) {
            return JsonVO.success(true);
        } else {
            return JsonVO.fail(false);
        }
    }

@Override
    public Boolean modifyRole(RoleDTO roleDTO) {
        Role role = new Role();
        role.setId(roleDTO.getId());
        role.setName(roleDTO.getName());
        role.setKeyword(roleDTO.getKeyword());
        role.setDescription(roleDTO.getDescription());
        role.setIsEnable(roleDTO.getIsEnable());
        role.setCreator(roleDTO.getCreator());
        role.setCreateTime(roleDTO.getCreateTime());
        role.setUpdateTime(new Date(System.currentTimeMillis()));
        UpdateWrapper<Role> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("name", roleDTO.getName());
        int updateNum = baseMapper.update(role, updateWrapper);

        return updateNum != 0;

    }

流程:

首先,modifyRole 方法接收一个 RoleDTO 对象,并进行输入验证:

检查角色的 id 是否为空,以及角色描述 description 的长度是否超过50个字符。

如果验证失败,直接返回失败响应。接着,调用 roleService.modifyRole(dto) 方法进行角色的更新操作。

modifyRole 方法中,首先创建一个 Role 对象,并将 RoleDTO 中的属性设置到 Role 对象上。

然后,使用 UpdateWrapper 设置更新条件(根据角色名称进行匹配),调用 baseMapper.update() 执行更新操作。

如果更新成功(即更新的行数不为0),则返回 true,否则返回 false

4  查询角色

代码:

@ApiOperation(value = "查看角色详情")
    @GetMapping("/query-one")
    @Override
    public JsonVO<RoleDTO> queryById(
            @ApiParam(value = "角色id", required = true, example = "1") @RequestParam String id) {
        return JsonVO.success(roleService.querRoleById(id));
    }
@Override
    public RoleDTO querRoleById(String id) {
        Role role = baseMapper.selectById(id);
        if (role == null) {
            return null;
        }

        return msRoleMapper.roleToRoleDto(role);
    }

流程:

这段代码的逻辑实现了通过角色ID查询角色详情的功能。

首先,queryById 方法作为接口的一部分,通过 @GetMapping 注解指定了请求的路径和方式,接收一个 id 参数,该参数代表角色的唯一标识。

在方法内部,调用 roleService.querRoleById(id) 以获取角色信息。如果找到了对应的角色数据,

则通过 msRoleMapper.roleToRoleDto(role)Role 实体对象转换为 RoleDTO 数据传输对象返回;

如果没有找到该角色,返回 null

最终,queryById 方法会封装查询结果到 JsonVO 对象中并返回,作为API的响应。

5  为角色分配菜单

代码:

 /**
     * @param dto 用户菜单对象
     * @return
     * @author sleepea
     */
    @PostMapping("/assign-menus")
    @ApiOperation(value = "角色分配菜单")
    @Override
    public JsonVO<Boolean> assignMenus(@RequestBody RoleMenuDTO dto) {
        JsonVO<Boolean> json = new JsonVO<>();
        try {
            // 查询相应的角色和菜单,以确保它们存在且可以被操作
            if (!roleMenuService.checkRoleMenu(dto)) {
                json.setData(false);
                json.setMessage("不存在此用户或者此菜单");
                return json;
            }
            // 检索指定角色的现有菜单 ID 列表
            List<String> existingMenuIds = roleMenuService.getMenuIdsByRoleId(dto.getRoleId());
            // 查看是否已存在此菜单ID
            if (existingMenuIds.contains(dto.getMenuId())) {
                json.setData(false);
                json.setMessage("当前用户已被分配此菜单");
                return json;
            }
            // 将新的菜单 ID 添加到用户菜单表中
            boolean result = roleMenuService.assignMenus(dto);
            // 返回结果
            json.setData(result);
            json.setMessage(result ? "分配菜单成功" : "分配菜单失败");
            return json;
        } catch (Exception e) {
            json.setData(false);
            json.setMessage("分配菜单失败");
            return json;
        }
    }
@Service
public class RoleMenuServiceImpl implements RoleMenuService {
    @Resource
    private RoleMenuMapper roleMenuMapper;

    @Override
    @Transactional
    public Boolean assignMenus(RoleMenuDTO dto) {
        // 调用mapper层的assignMenu方法来将菜单ID添加到角色的菜单列表中
        return roleMenuMapper.assignMenu(dto.getRoleId(), dto.getMenuId()) > 0;
    }
    @Override
    @Transactional
    public List<String> getMenuIdsByRoleId(String roleId){
        // 调用mapper层的getMenuIdsByRoleId方法来从角色的菜单列表中获取已有的menuId
        return roleMenuMapper.getMenuIdsByRoleId(roleId);
    }

    @Override
    @Transactional
    public Boolean checkRoleMenu(RoleMenuDTO dto) {
        return roleMenuMapper.checkRoleMenu(dto.getRoleId(), dto.getMenuId()) > 0;
    }
}
@Mapper
public interface RoleMenuMapper {

    int assignMenu(@Param("roleId") String roleId, @Param("menuId") String menuId);

    List<String> getMenuIdsByRoleId(@Param("roleId") String roleId);

    int checkRoleMenu(@Param("roleId") String roleId, @Param("menuId") String menuId);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zeroone.star.sysmanager.mapper.RoleMenuMapper">

    <insert id="assignMenu">
        INSERT INTO zo_role_menu(role_id, menu_id)
        VALUES (#{roleId},#{menuId})
    </insert>

    <select id="getMenuIdsByRoleId" resultType="string">
        SELECT menu_id
        FROM  zo_role_menu
        WHERE role_id = #{roleId}
    </select>

    <select id="checkRoleMenu" resultType="int">
        SELECT COUNT(*)
        FROM zo_role r, zo_menu m
        WHERE r.id = #{roleId} AND m.id = #{menuId}
    </select>

</mapper>

流程:

assignMenus 方法接收一个 RoleMenuDTO 对象,

并通过 roleMenuService.checkRoleMenu(dto) 检查角色和菜单是否存在,如果不存在则返回错误信息。

接下来,方法通过 roleMenuService.getMenuIdsByRoleId(dto.getRoleId()) 获取当前角色已分配的菜单列表,检查是否已经分配了指定的菜单。如果已分配,则返回相应提示。

如果未分配,则调用 roleMenuService.assignMenus(dto) 执行菜单分配操作,并根据操作结果返回成功或失败的状态。

若在过程中发生异常,则捕获并返回失败信息。

四  菜单模块业务

1  建立新的菜单

代码:

@ApiOperation(value = "建立新的菜单")
    @PostMapping("/add-menu")
    @Override
    public JsonVO<Boolean> addMenu(MenuDTO dto) {
        return menuService.addMenu(dto);
    }
//新增菜单
    @Override
    public JsonVO<Boolean> addMenu(MenuDTO dto) {
        // 判断 parent_menu_id 是否存在
        String parent_menu_id = dto.getParent_menu_id();
        boolean parent_menu_idNotExist = Objects.isNull(getById(parent_menu_id));
        if (parent_menu_idNotExist) {
            return JsonVO.create(false, ResultStatus.PARAMS_INVALID.getCode(), "parent menu id 不存在");
        }
        // 将 dto 的非空属性赋值给 menu 权限,然后添加权限
        Menu menu = new Menu();
        BeanUtil.copyProperties(dto, menu, CopyOptions.create().setIgnoreNullValue(true));
        menu.setId(String.valueOf(snowflake.nextId()));
        try {
            menu.setCreator(userHolder.getCurrentUser().getUsername());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        Date date = new Date(System.currentTimeMillis());
        menu.setCreateTime(date);
        menu.setUpdateTime(date);
        boolean result = save(menu);
        if (result) {
            return JsonVO.success(true);
        }
        return JsonVO.fail(false);
    }

流程:

首先,它会检查传入的 parent_menu_id 是否存在,如果不存在则返回参数无效的错误信息。

接着,将 MenuDTO 对象中的非空属性复制到一个新的 Menu 对象中,并为其生成唯一的ID,设置创建者信息(从当前用户获取)以及创建和更新时间。

最后,调用 save() 方法保存菜单信息,如果保存成功则返回成功的响应,否则返回失败的响应。

2  修改菜单

@ApiOperation(value = "修改菜单")
    @PostMapping("/modify-menu")
    @Override
    public JsonVO<Boolean> modifyMenu(MenuDTO dto) {
        return menuService.modifyMenu(dto);
    }

//修改菜单
    @Override
    public JsonVO<Boolean> modifyMenu(MenuDTO dto) {
        String id = dto.getId();
        Menu menu = getById(id);
        if (Objects.isNull(menu)) {
            return JsonVO.create(false, ResultStatus.PARAMS_INVALID.getCode(), "权限不存在");
        }
        // 判断 parent_menu_id 是否存在
        String parent_menu_id = dto.getParent_menu_id();
        boolean parent_menu_idNotExist = Objects.isNull(getById(parent_menu_id));
        if (parent_menu_idNotExist) {
            return JsonVO.create(false, ResultStatus.PARAMS_INVALID.getCode(), "parent menu id 不存在");
        }
        // 将 dto 的非空属性赋值给 menu 权限,然后更新权限
        BeanUtil.copyProperties(dto, menu, CopyOptions.create().setIgnoreNullValue(true));
        menu.setUpdateTime(new Date(System.currentTimeMillis()));
        boolean result = updateById(menu);
        if (result) {
            return JsonVO.success(true);
        }
        return JsonVO.fail(false);
    }

流程:

首先,它根据传入的 id 获取对应的菜单对象,如果该菜单不存在,则返回“权限不存在”的错误信息。

接着,检查传入的 parent_menu_id 是否存在,如果不存在,则返回错误提示。

然后,将 MenuDTO 对象中的非空属性更新到已有的 Menu 对象中,并设置更新时间。

最后,调用 updateById() 方法更新菜单信息,如果更新成功,则返回成功的响应,否则返回失败的响应。

3  删除菜单

代码:

@ApiOperation(value = "删除菜单")
    @DeleteMapping("/remove-menu")
    @Override
    public JsonVO<Boolean> removeMenu(@RequestParam String id) {
        return menuService.removeMenu(id);
    }
//删除菜单
    @Override
    public JsonVO<Boolean> removeMenu(String id) {
        if (Menu_ROOT_ID.equals(id)) {
            // 不能删除根权限
            return JsonVO.create(false, ResultStatus.FORBIDDEN.getCode(), "不能删除主菜单");
        }
        Menu menu = getById(id);
        if (Objects.isNull(menu)) {
            return JsonVO.create(false, ResultStatus.PARAMS_INVALID.getCode(), "菜单不存在");
        }
        boolean result = removeById(menu);

        if (result) {
            return JsonVO.success(true);
        }
        return JsonVO.fail(false);
    }

流程:

首先,@DeleteMapping("/remove-menu") 映射了一个 HTTP DELETE 请求,当客户端请求删除某个菜单时,removeMenu 方法被调用。

方法首先检查传入的菜单 ID 是否为根菜单 ID(Menu_ROOT_ID),如果是,则返回一个错误提示,表示根菜单不能被删除。

接着,方法尝试通过 getById(id) 查找对应的菜单对象。

如果菜单不存在,则返回参数无效的错误提示。

如果菜单存在,接着调用 removeById(menu) 删除该菜单,删除成功则返回 true,否则返回删除失败的响应。

整个过程通过 JsonVO 封装了响应的状态和数据,确保客户端能够获取到详细的操作结果。

4  查询菜单

里面包含分页查询和模糊查询

五  用户模块

1  添加用户

代码:

@ApiOperation(value = "添加用户")
    @PostMapping("add")
    @Override
    public JsonVO<Boolean> addUser(@Validated UserDTO dto) {
        List<User> list = userService.list(new LambdaQueryWrapper<User>().eq(User::getUsername, dto.getUsername()));
        if (list.size() > 0) {
            return JsonVO.create(false, ResultStatus.PARAMS_INVALID.getCode(), "已经有该用户了");
        }
        return userService.saveUser(dto) ? JsonVO.success(true) : JsonVO.fail(false);
    }
@Override
    public Boolean saveUser(UserDTO dto) {
        User user = new User();
        try {
            String username = userHolder.getCurrentUser().getUsername();
            dto.setCreator(username);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        dto.setPassword(passwordEncoder.encode(dto.getPassword()));
        try {
            BeanUtil.copyProperties(dto, user);
            user.setId(snowflake.nextIdStr());
            LocalDateTime now = LocalDateTime.now();
            user.setRegistTime(now);
            user.setUpdateTime(now);
            return baseMapper.insert(user) >= 1;
        } catch (Exception e) {
            return false;
        }
    }

流程:

首先,addUser 方法接收一个 UserDTO 对象并进行验证。它通过查询数据库检查是否已存在具有相同用户名的用户,如果找到重复的用户名,则返回错误信息。

若用户名唯一,则调用 userService.saveUser(dto) 方法保存新用户。

saveUser 方法中,

首先获取当前用户的用户名并设置为新用户的创建者。

然后,使用 BCryptPasswordEncoder 对用户输入的密码进行加密。

接着,通过 BeanUtil.copyPropertiesUserDTO 的属性复制到 User 对象中,并设置用户的唯一 ID、注册时间和更新时间。

最后,将用户信息插入数据库,并根据插入结果返回 truefalse,表明用户添加是否成功。

2  删除用户

代码:

@ApiOperation(value = "删除用户")
    @DeleteMapping("delete")
    @Override
    public JsonVO<Boolean> deleteUser(@RequestParam String id) {
        return userService.removeUser(id) ? JsonVO.success(true) : JsonVO.fail(false);
    }
@Override
    public Boolean removeUser(String id) {
        return baseMapper.deleteById(id) >= 1;
    }

3  修改用户

代码:

@ApiOperation(value = "修改用户")
    @PutMapping("modify")
    @Override
    public JsonVO<Boolean> modifyUser(@Validated UserDTO dto) {
        return userService.updateUser(dto) ? JsonVO.success(true) : JsonVO.fail(false);
    }
@Override
    public Boolean updateUser(UserDTO dto) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getId, dto.getId());
        User user = new User();
        BeanUtil.copyProperties(dto, user);
        LocalDateTime now = LocalDateTime.now();
        user.setUpdateTime(now);
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        user.setPassword(passwordEncoder.encode(dto.getPassword()));
        return baseMapper.update(user, wrapper) >= 1;
    }

流程:

首先,modifyUser 方法接收一个 UserDTO 对象,并调用 userService.updateUser(dto) 更新用户信息。如果更新成功,返回成功响应;否则返回失败响应。

updateUser 方法中,首先通过 LambdaQueryWrapper 构造查询条件,确保更新操作只作用于指定 ID 的用户。

然后,将 UserDTO 中的数据复制到 User 实体对象中,并更新用户的 updateTime 为当前时间。

密码字段通过 BCryptPasswordEncoder 进行加密处理,以确保安全性。

最后,调用数据库的 update 方法执行更新操作,并根据更新结果返回 truefalse,表明更新是否成功。

4  查询角色

也是模糊查询和分页查询

5  为用户分配角色

代码:

@ApiOperation(value = "分配角色")
    @PutMapping("assignRole")
    @Override
    public JsonVO<Boolean> assignRole(UserRoleDTO dto) {
        return userService.assignRole(dto) ? JsonVO.success(true) : JsonVO.fail(false);
    }
@Override
    public Boolean assignRole(UserRoleDTO dto) {
        return userRoleMapper.assignRole(dto.getUserId(), dto.getRoleId()) > 0;
    }
int assignRole(@Param("roleId") String roleId, @Param("menuId") String menuId);
<insert id="assignRole">
        INSERT INTO zo_user_role(user_id, role_id)
        VALUES (#{user_id},#{role_id})
    </insert>

流程:

传入用户id和角色id,然后修改user_role那张表,将用户和角色联系到一起

主要难点

1  Redis缓存

通过引入Redis缓存技术,解决用户频繁查询缓存权限,以缓解高并发 情况下MySQL数据库性能瓶颈;

1. 缓存登录状态 (authLogin)

用户登录时,首先通过验证码和账号密码认证,获取到OAuth2的Token,然后将该Token存储在Redis中。具体流程如下:

// 4.将授权token存储到Redis中,记录登录状态
String token = oauth2TokenDTO.getData().getToken();
// 4.1 拼接key
String userTokenKey = RedisConstant.USER_TOKEN + ":" + token;
// 4.2 判断Redis是否成功存储该Token,设置过期时间1小时
if (redisUtils.add(userTokenKey, 1, 1L, TimeUnit.HOURS) < 0) {
    return fail(oauth2TokenDTO.getData(), ResultStatus.SERVER_ERROR);
}
在登录时,通过 redisUtils.add方法将用户的登录状态保存在Redis中,且设置了1小时的过期时间。这意味着每次用户登录时,都会在Redis中保存一个唯一的 token,表示用户已登录。

2   缓存用户信息 (getCurrUser)

在获取当前用户信息时,首先会判断用户的Token是否存在于Redis中。如果存在,直接通过Redis获取用户信息,如果不存在,则可能需要查询数据库。
// 1. 判断缓存中是否存在对应token
String tokenKeyInRedis = RedisConstant.USER_TOKEN + ":" + userHolder.getCurrentUserToken();
if (!redisUtils.isExist(tokenKeyInRedis)) {
    return fail(null, ResultStatus.UNAUTHORIZED);
}

// 如果Token存在,执行获取用户信息的逻辑
UserDTO currentUser = userHolder.getCurrentUser();

3  缓存退出登录状态 (logout)

退出登录时,通过Redis删除用户的Token。

String tokenKeyInRedis = RedisConstant.USER_TOKEN + ":" + userHolder.getCurrentUserToken();
if (!redisUtils.isExist(tokenKeyInRedis)) {
    return fail(null, ResultStatus.UNAUTHORIZED);
}

// 删除Token,表示用户退出登录
int del = redisUtils.del(userTokenKey);
if (del < 0) {
    return JsonVO.fail("退出失败!");
}

2  ElasticSearch存储

用户信息在系统中频繁涉及,尤其是在查询用户列表、模糊搜索用户、查看用户详情等操作时。

储存用户名字


                

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【PHP】部署和发布PHP网站到IIS服务器

欢迎来到《小5讲堂》 这是《PHP》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录 前言安装PHP稳定版本线程安全版解压使用 PHP配置配置文件扩展文件路径…

Docker安装RabbitMq详细教程

1.1通过Docker pull RabbitMq docker pull rabbitmq 1.2 获取镜像 docker images 注&#xff1a;执行1.3之前请使用以下命令创建docker网络 docker network create tm 1.3运行命令启动参数 docker run \-e RABBITMQ_DEFAULT_USERrabbitmq \-e RABBITMQ_DEFAULT_PASSrabbitm…

华为ENSP--IP编址及静态路由配置

项目拓扑 项目任务 一、基础配置和IP编址 在AR1、AR2、AR3上配置设备名称和IP地址 # AR1配置 [AR1]interface GigabitEthernet 0/0/0 [AR1-GigabitEthernet0/0/0]ip address 10.0.13.1 24 [AR1-GigabitEthernet0/0/0]q [AR1]interface GigabitEthernet 0/0/1 [AR1-GigabitEth…

老北京香酥芝麻饼

宝安石岩上屋大道有一家老北京香酥芝麻饼&#xff0c;不仅很好吃&#xff0c;还分量特别厚实。应该这家老店&#xff0c;在上屋大道很多人知道和吃过。我每周末都会去买回去给家人一起吃。工作日由于上下班&#xff0c;想买也买不了&#xff0c;因为太晚去老板就收摊了。就像早…

对于相对速度的重新理解 - 2

回到先前说的&#xff0c;先令真空光速为标准光速&#xff0c; 光子的绝对速度 范围&#xff0c; 物质粒子的 范围&#xff0c; 这样的话&#xff0c;我们就可以根据 和 &#xff0c;把速度分成3个段&#xff0c; 这样就可以出现速度和它的负值&#xff0c;也就是速度的矢量具…

GWO-SVMD分解 | Matlab实现GWO-SVMD灰狼算法优化逐次变分模态分解

GWO-SVMD分解 | Matlab实现GWO-SVMD灰狼算法优化逐次变分模态分解 目录 GWO-SVMD分解 | Matlab实现GWO-SVMD灰狼算法优化逐次变分模态分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 GWO-SVMD灰狼算法优化逐次变分模态分解 内有15种用以优化svmd的适应度函数&#…

初识Linux—— 基本指令(下)

前言&#xff1a; 本篇继续来学习Linux的基础指令&#xff0c;继续加油&#xff01;&#xff01;&#xff01; 本篇文章对于图片即内容详解&#xff0c;已同步到本人gitee&#xff1a;Linux学习: Linux学习与知识讲解 Linux指令 1、查看文件内容的指令 cat ​ cat 查看文件…

在SQLyog中导入和导出数据库

导入 假如我要导入一个xxx.sql&#xff0c;我就先创建一个叫做xxx的数据库。 然后右键点击导入、执行SQL脚本 选择要导入的数据库文件的位置&#xff0c;点击执行即可 注意&#xff1a; 导入之后记得刷新一下导出 选择你要导出的数据库 右键选择&#xff1a;备份/导出、…

如何进行高级红队测试:OpenAI的实践与方法

随着人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;AI模型的安全性和可靠性已经成为业界关注的核心问题之一。为了确保AI系统在实际应用中的安全性&#xff0c;红队测试作为一种有效的安全评估方法&#xff0c;得到了广泛应用。近日&#xff0c;OpenAI发布了两…

ES 基本使用与二次封装

概述 基本了解 Elasticsearch 是一个开源的分布式搜索和分析引擎&#xff0c;基于 Apache Lucene 构建。它提供了对海量数据的快速全文搜索、结构化搜索和分析功能&#xff0c;是目前流行的大数据处理工具之一。主要特点即高效搜索、分布式存储、拓展性强 核心功能 全文搜索:…

Java项目实战II基于SPringBoot的玩具销售商城管理系统(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。获取源码联系方式请查看文末 一、前言 随着儿童娱乐与教育需求的…

Python安装出现严重错误的解决方法_0x80070643-安装时发生严重错误

使用这个软件MicrosoftProgram_Install_and_Uninstall.meta.diagcab把关于Python一个个组件全部删除&#xff0c;然后就能够重新安装Python了 修复阻止程序安装或删除的问题 - Microsoft 支持 这里下载

Java语言编程,通过阿里云mongo数据库监控实现数据库的连接池优化

一、背景 线上程序连接mongos超时&#xff0c;mongo监控显示连接数已使用100%。 java程序报错信息&#xff1a; org.mongodb.driver.connection: Closed connection [connectionId{localValue:1480}] to 192.168.10.16:3717 because there was a socket exception raised by…

微信小程序全局配置:导航栏、下拉刷新与上拉触底设置教程

微信小程序全局配置:导航栏、下拉刷新与上拉触底设置教程 引言 微信小程序作为一种新兴的轻量级应用,凭借其便捷性和丰富的功能受到了广泛的欢迎。在开发小程序的过程中,合理配置全局属性是提升用户体验的关键。本文将深入探讨小程序的全局配置中的window选项,重点介绍导…

YOLOv11融合[ECCV 2018]RCAN中的RCAB模块及相关改进思路

YOLOv11v10v8使用教程&#xff1a; YOLOv11入门到入土使用教程 YOLOv11改进汇总贴&#xff1a;YOLOv11及自研模型更新汇总 《Image Super-Resolution Using Very Deep Residual Channel Attention Networks》 一、 模块介绍 论文链接&#xff1a;https://arxiv.org/abs/1807…

linux ubuntu的脚本知

目录 一、变量的引用 二、判断指定的文件是否存在 三、判断目录是否存在 四、判断最近一次命令执行是否成功 五、一些比较符号 六、"文件"的读取和写入 七、echo打印输出 八、ubuntu切换到root用户 N、其它可以参考的网址 脚本功能强大&#xff0c;用起来也…

前端:JavaScript (学习笔记)【2】

目录 一&#xff0c;数组的使用 1&#xff0c;数组的创建 [ ] 2&#xff0c;数组的元素和长度 3&#xff0c;数组的遍历方式 4&#xff0c;数组的常用方法 二&#xff0c;JavaScript中的对象 1&#xff0c;常用对象 &#xff08;1&#xff09;String和java中的Stri…

【Git】工作区、暂存区和版本库

目录 一、基本概念&#xff1a; 关系图&#xff1a; 1. 工作区&#xff08;Working Directory&#xff09; $ 1.1 工作区功能 $ 1.2 工作区特点 2. 暂存区&#xff08;Staging Area&#xff09; $ 2.1 暂存区功能 $ 2.2 暂存区特点 $ 2.3 常用命令 3. 版本库&#xff08…

【Linux | 计网】TCP协议详解:从定义到连接管理机制

目录 1.TCP协议的定义&#xff1a; 2.TCP 协议段格式 3.TCP两种通信方式 4.确认应答(ACK)机制 解决“后发先至”问题 5.超时重传机制 那么, 超时的时间如何确定? 6.连接管理机制&#xff1a; 6.1.三次握手&#xff1a; 为什么需要3次握手&#xff0c;一次两次不行吗…

Springboot系列之:创建Springboot项目,Springboot整合MyBatis-plus

Springboot系列之&#xff1a;创建Springboot项目&#xff0c;Springboot整合MyBatis-plus 一、快速创建Spring boot项目二、项目完整目录三、pom.xml四、application.yaml五、实体类六、mapper七、IService接口八、Service实现类九、配置类十、枚举十一、增删改查测试类十二、…