人力资源项目学习

news/2024/11/30 4:15:14/

特点

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

好处

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

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

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

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

1  首先先定义节点对象TreeNode 
java">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*/@Setterprotected String tnId;/*** 节点父节点ID*/@Setterprotected String tnPid;/*** 节点深度*/@Setterprotected 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将其他对象转化为节点对象

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

java">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 

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

java">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

java">/*** The implementation of {@link RightService}, base on {@link ServiceImpl}** @author authoralankay* @see RightService* @see ServiceImpl*/class RightTreeNodMapper implements TreeNodeMapper<Right> {@Overridepublic 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方法

相应代码:

java">@ApiOperation(value = "授权登录")@PostMapping("auth-login")@Overridepublic 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.调用远程接口,获取TokenJsonVO<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拼接keyString userTokenKey = RedisConstant.USER_TOKEN + ":" + token;//4.2逻辑判断if (redisUtils.add(userTokenKey, 1, 1L, TimeUnit.HOURS) < 0) {return fail(oauth2TokenDTO.getData(), ResultStatus.SERVER_ERROR);}//返回结果tokenreturn 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:

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

Service:

java">@Overridepublic 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)

代码:

java">@ApiOperation(value = "退出登录")@GetMapping("logout")@Overridepublic JsonVO<String> logout() throws Exception {//1.判断缓存中是否存在对应tokenString tokenKeyInRedis = RedisConstant.USER_TOKEN + ":" + userHolder.getCurrentUserToken();if (!redisUtils.isExist(tokenKeyInRedis)) {//不存在return fail(null, ResultStatus.UNAUTHORIZED);}//存在//登出逻辑,需要配合登录逻辑实现//1.获取当前用户tokenString currentUserToken = userHolder.getCurrentUserToken();//2.拼接keyString userTokenKey = RedisConstant.USER_TOKEN + ":" + currentUserToken;//2.删除当前用户tokenint 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)

代码:

java">@ApiOperation(value = "获取当前用户")@GetMapping("current-user")@Overridepublic JsonVO<LoginVO> getCurrUser() {//1.判断缓存中是否存在对应tokenString 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());//设置用户idcurrentUser.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  修改权限

代码:

java">@ApiOperation(value = "修改权限")@PostMapping("/modify-right")@Overridepublic JsonVO<Boolean> modifyRight(RightDTO dto) {return rightService.modifyRight(dto);}
java">@Overridepublic 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  增加权限(雪花算法)

代码:

java">@ApiOperation(value = "增加权限")@PostMapping("/add-right")@Overridepublic JsonVO<Boolean> addRight(RightDTO dto) {return rightService.addRight(dto);}
java">@Overridepublic 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

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

service

java">@Overridepublic 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  查询权限(树结构节点)

代码:

java">@ApiOperation(value = "查询权限结构树")@GetMapping("/query-right-tree")@Overridepublic 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);}
java">@Overridepublic 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  新增角色

代码实现:

java">@ApiOperation(value = "新增角色")@PostMapping("/add-one")@Overridepublic 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);}}

java">@Overridepublic 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删除角色

java">@ApiOperation(value = "删除角色")@DeleteMapping("/delete")@Overridepublic 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);}}
java">@Overridepublic Boolean deleRoleById(String id) {int result = baseMapper.deleteById(id);return result == 1;}

3  修改角色

代码:

java">@ApiOperation(value = "修改角色")@PostMapping("/modify")@Overridepublic 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);}}

java">@Overridepublic 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  查询角色

代码:

java">@ApiOperation(value = "查看角色详情")@GetMapping("/query-one")@Overridepublic JsonVO<RoleDTO> queryById(@ApiParam(value = "角色id", required = true, example = "1") @RequestParam String id) {return JsonVO.success(roleService.querRoleById(id));}
java">@Overridepublic 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  为角色分配菜单

代码:

java"> /*** @param dto 用户菜单对象* @return* @author sleepea*/@PostMapping("/assign-menus")@ApiOperation(value = "角色分配菜单")@Overridepublic 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());// 查看是否已存在此菜单IDif (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;}}
java">@Service
public class RoleMenuServiceImpl implements RoleMenuService {@Resourceprivate RoleMenuMapper roleMenuMapper;@Override@Transactionalpublic Boolean assignMenus(RoleMenuDTO dto) {// 调用mapper层的assignMenu方法来将菜单ID添加到角色的菜单列表中return roleMenuMapper.assignMenu(dto.getRoleId(), dto.getMenuId()) > 0;}@Override@Transactionalpublic List<String> getMenuIdsByRoleId(String roleId){// 调用mapper层的getMenuIdsByRoleId方法来从角色的菜单列表中获取已有的menuIdreturn roleMenuMapper.getMenuIdsByRoleId(roleId);}@Override@Transactionalpublic Boolean checkRoleMenu(RoleMenuDTO dto) {return roleMenuMapper.checkRoleMenu(dto.getRoleId(), dto.getMenuId()) > 0;}
}
java">@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_idFROM  zo_role_menuWHERE role_id = #{roleId}</select><select id="checkRoleMenu" resultType="int">SELECT COUNT(*)FROM zo_role r, zo_menu mWHERE r.id = #{roleId} AND m.id = #{menuId}</select></mapper>

流程:

assignMenus 方法接收一个 RoleMenuDTO 对象,

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

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

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

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

四  菜单模块业务

1  建立新的菜单

代码:

java">@ApiOperation(value = "建立新的菜单")@PostMapping("/add-menu")@Overridepublic JsonVO<Boolean> addMenu(MenuDTO dto) {return menuService.addMenu(dto);}
java">//新增菜单@Overridepublic 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  修改菜单

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

java">//修改菜单@Overridepublic 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  删除菜单

代码:

java">@ApiOperation(value = "删除菜单")@DeleteMapping("/remove-menu")@Overridepublic JsonVO<Boolean> removeMenu(@RequestParam String id) {return menuService.removeMenu(id);}
java">//删除菜单@Overridepublic 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  添加用户

代码:

java">@ApiOperation(value = "添加用户")@PostMapping("add")@Overridepublic 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);}
java">@Overridepublic 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  删除用户

代码:

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

3  修改用户

代码:

java">@ApiOperation(value = "修改用户")@PutMapping("modify")@Overridepublic JsonVO<Boolean> modifyUser(@Validated UserDTO dto) {return userService.updateUser(dto) ? JsonVO.success(true) : JsonVO.fail(false);}
java">@Overridepublic 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  为用户分配角色

代码:

java">@ApiOperation(value = "分配角色")@PutMapping("assignRole")@Overridepublic JsonVO<Boolean> assignRole(UserRoleDTO dto) {return userService.assignRole(dto) ? JsonVO.success(true) : JsonVO.fail(false);}
java">@Overridepublic Boolean assignRole(UserRoleDTO dto) {return userRoleMapper.assignRole(dto.getUserId(), dto.getRoleId()) > 0;}
java">int assignRole(@Param("roleId") String roleId, @Param("menuId") String menuId);
java"><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中。具体流程如下:

java">// 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获取用户信息,如果不存在,则可能需要查询数据库。
java">// 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。

java">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存储

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

储存用户名字


http://www.ppmy.cn/news/1551092.html

相关文章

如何利用ArcGIS探究环境和生态因子对水体、土壤和大气污染物的影响?

原文&#xff1a;如何利用ArcGIS探究环境和生态因子对水体、土壤和大气污染物的影响&#xff1f;https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247630247&idx8&sn2debedc63a42cfd24ed4c8afbb8c575d&chksmfa8dbc40cdfa3556dc0ec660d00fcd7e8c9a9ca75a8…

Web登录页面设计

记录第一个前端界面&#xff0c;暑假期间写的&#xff0c;用了Lottie动画和canvas标签做动画&#xff0c;登录和注册也连接了数据库。 图片是从网上找的&#xff0c;如有侵权私信我删除&#xff0c;谢谢啦~

网络安全内容整理一

前言 整理博客&#xff0c;统一到常用的站点。 基础知识 网络安全的三个基本属性&#xff1a;CIA三元组 机密性 Confidentiality完整性 Integrity可用性 Availability 网络安全的基本需求 可靠性、可用性、机密性、完整性、不可抵赖性、可控性、可审查性、真实性 网络安…

小游戏聚合SDK的工具类封装

文章目录 前言工具类单例日志打印输入框的封装前言 之前的文章写了如何开发小游戏聚合SDK,既然是聚合SDK,工具类的封装也比较重要,做好基础搭建后续在接入其他渠道的时候能大大减少工作量。 工具类 单例 初始化的配置信息,比如应用ID 、渠道ID等需要全局使用,而且初始…

2024下半年——【寒假】自学黑客计划(网络安全)

CSDN大礼包&#xff1a;&#x1f449;基于入门网络安全/黑客打造的&#xff1a;&#x1f449;黑客&网络安全入门&进阶学习资源包 前言 什么是网络安全 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&a…

金铲铲S13双城之战自动拿牌助手

金铲铲S13双城之战自动拿牌助手 基于python&#xff0c;pyautogui和金铲铲自带备战助手实现 B站视频演示效果 shuangcheng.py import timeimport pyautogui import datetimeprint(请关注您的分辨率&#xff0c;此程序需要配合thumbs_x_y.txt文件同时使用) print(简介&#x…

【C++】数据类型(上)

C规定在创建一个变量或一个常量时&#xff0c;必须要指定出相应的数据类型&#xff0c;否则无法给变量分配内存 数据类型存在意义&#xff1a;给变量分配合适的内存空间。 1.1 整型 整型作用&#xff1a;整型变量表示的整数类型的数据。 C中能够表示整型类型的有以下几种…

android bindService打开失败

在写demo验证SurfaceControlViewHost的时候&#xff0c;bindService提示 Unable to start service Intent U0: not found 在源代码里搜了下&#xff0c;找到是在如下方法里面里面打印出来的 // frameworks/base/services/core/java/com/android/server/am/ActiveServices.java…