若依学习日记2
原创
2025-03-13 11:34
10
--- typora-copy-images-to: 若依学习2 --- ## 若依学习2 ### 1.前端使用的通讯方式是什么,传参有哪几种方式,如何进行统一请求处理的; 前端使用vue封装的axios函数进行request请求。 post,put请求参数为json格式的数据。 get请求的参数为query表单数据或无,请求拦截器实现参数拼接到url。请求拦截器在请求发送前进行解析,统一请求处理。 <img src="/upload/blog/若依学习2/image-20241022145225381.png" alt="image-20241022145225381" style="zoom:50%;" />  在request.js里面配置请求拦截器和响应拦截器, 如果需要token,在请求头headers里设置token。  如果get请求存在params就将params序列化到url   前端对post,put请求防重复提交,将请求放到sessionStorage中,判断地址,数据,间隔时间是否为相同请求。例子如下:  响应拦截器 401错误登录过期,发送logout请求,并重定向到登录页面,一般都是token过期。以及一系列的响应错误请求处理。 ### 2.在ruoyi框架中使用jaspyt实现数据库静态密码加密,附关键点截图(引包、配置、注解); 引包: ```java <!--添加 jasypt-spring-boot-starter 依赖--> <dependency> <groupId>com.github.ulisesbocchio</groupId> <artifactId>jasypt-spring-boot-starter</artifactId> <version>3.0.2</version> </dependency> ``` 配置: ```java jasypt: encryptor: password: ${JASYPT_ENCRYPTOR_PASSWORD:default_password} # 从环境变量读取 algorithm: ${JASYPT_ENCRYPTOR_ALGORITHM:PBEWithMD5AndDES} # 从环境变量或默认值读取 ```  加密后: ```java master: url: jdbc:mysql://localhost:3306/rouyi-vue-blog?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: ENC(ddKAznny+8W1cMvuDiXdJXp9CX1eSQoX) password: ENC(QKIV0tLqzzz7oM3FdCWi3kA5Sm0IaihUwSmojsP72O0=) ``` 注解: ```java package com.ruoyi.framework.config; import org.jasypt.encryption.StringEncryptor; import org.jasypt.encryption.pbe.PooledPBEStringEncryptor; import org.jasypt.encryption.pbe.config.PBEConfig; import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class CodeSheepEncryptorConfig { @Value("${jasypt.encryptor.password}") // 从配置文件中获取密钥 private String secretKey; @Value("${jasypt.encryptor.algorithm}") private String ALGORITHM; /** * 配置并返回一个 {@link StringEncryptor} 实例用于加解密操作. * 本方法旨在为应用提供一个加解密器实例,以方便地对字符串内容进行加密和解密. * 使用同一密钥(secretKey)对同一内容加密会生成不同的密文,但解密这些密文将恢复原始内容. * Jasypt 默认使用 {@link StringEncryptor} 解密全局配置文件中的属性,因此在提供密文时,也需要提供 {@link StringEncryptor} 加密的密文. */ @Bean public PooledPBEStringEncryptor mystringEncryptor() { PooledPBEStringEncryptor encryptor = new PooledPBEStringEncryptor(); encryptor.setConfig(getSimpleStringPBEConfig()); return encryptor; } /** * 设置 {@link PBEConfig} 配置对象,SimpleStringPBEConfig 是它的实现类 * 1、所有的配置项建议与全局配置文件中的配置项保持一致,特别是 password、algorithm 等等选项,如果不一致,则应用启动时解密失败而报错. * 2、setPassword(final String password):设置加密密钥,必须与全局配置文件中配置的保存一致,否则应用启动时会解密失败而报错. * 3、setPoolSize(final String poolSize):设置要创建的加密程序池的大小. * 4、setAlgorithm(final String algorithm): 设置加密算法的值, 此算法必须由 JCE 提供程序支持 * 5、setKeyObtentionIterations: 设置应用于获取加密密钥的哈希迭代次数。 * 6、setProviderName(final String providerName):设置要请求加密算法的安全提供程序的名称 * 7、setSaltGeneratorClassName:设置 Sal 发生器 * 8、setIvGeneratorClassName:设置 IV 发生器 * 9、setStringOutputType:设置字符串输出的编码形式。可用的编码类型有 base64、hexadecimal * @return */ private SimpleStringPBEConfig getSimpleStringPBEConfig() { SimpleStringPBEConfig config = new SimpleStringPBEConfig(); config.setPassword(secretKey); config.setPoolSize("1"); config.setAlgorithm(ALGORITHM); config.setKeyObtentionIterations("1000"); config.setProviderName("SunJCE"); config.setSaltGeneratorClassName("org.jasypt.salt.RandomSaltGenerator"); config.setIvGeneratorClassName("org.jasypt.iv.RandomIvGenerator"); config.setStringOutputType("base64"); return config; } } ``` ### 3.了解restful规范,实现get/post/delete/put方式的请求各一项,并了解它们分别用于什么场景; **GET 请求** 对应 **SELECT 语句**: - 用于从数据库中读取数据或资源。 - 场景:获取一条或多条记录,例如查询用户信息、获取文章列表等。 ```java @GetMapping(value = "/{id}") public ResponseEntity<List<String>> getPermissionById(@PathVariable("id") Long id) { List<String> permissions = permissionService.getPermissionById(id); if (permissions == null || permissions.isEmpty()) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(null); // 404 Not Found } return ResponseEntity.ok(permissions); // 200 OK and return the permissions list } ``` **POST 请求** 对应 **CREATE 语句**: - 用于向数据库中插入新数据。 - 场景:创建新的资源,例如新增用户、发布新的文章、创建订单等。 ```java @PostMapping() public ResponseEntity<AjaxResult> addRolePermission(@Validated @RequestBody SysRole role) { Long roleId = role.getRoleId(); Long[] menuIds = role.getMenuIds(); try { permissionService.addUserPermission(roleId, menuIds); return ResponseEntity.status(HttpStatus.CREATED).body(AjaxResult.success("权限添加成功")); // } catch (IllegalArgumentException e) { // return ResponseEntity.badRequest().body(AjaxResult.error(e.getMessage())); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(AjaxResult.error("权限添加失败")); } } ``` ```java @Override public boolean addRolePermission(Long roleId, Long[] menuIds) { // 检查用户存在 if (!checkUserExists(roleId)) { throw new IllegalArgumentException("用户不存在"); } // 检查菜单存在性 for (Long menuId : menuIds) { if (!checkMenuExists(menuId)) { throw new IllegalArgumentException("菜单 ID " + menuId + " 不存在"); } } // 调用持久化方法来添加权限 return addRolePermissionToDb(roleId, menuIds); } ```  **DELETE 请求** 对应 **DELETE 语句**: - 用于从数据库中删除现有的数据或资源。 - 场景:删除指定的资源,例如删除用户、删除文章、取消订单等。 ```java @DeleteMapping public ResponseEntity<AjaxResult> deleteRolePermission(@RequestBody SysRole role) { Long roleId = role.getRoleId(); Long[] menuIds = role.getMenuIds(); try { permissionService.deleteRolePermission(roleId, menuIds); return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); // 删除成功返回 204 No Content // } catch (IllegalArgumentException e) { // return ResponseEntity.badRequest().body(AjaxResult.error(e.getMessage())); } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(AjaxResult.error("权限删除失败")); } } ``` ```java @Override public boolean deleteRolePermission(Long roleId, Long[] menuIds) { if(!checkUserExists(roleId)){ throw new IllegalArgumentException("用户不存在"); } // 检查传入的 menuIds 是否已存在对应的权限 int existingPermissions = countRolePermissions(roleId, Arrays.asList(menuIds)); if (existingPermissions == 0) { throw new IllegalArgumentException("该用户没有对应的权限"); } return deleteRolePermissionToDB(roleId,Arrays.asList(menuIds))>=menuIds.length; } ``` **PUT 请求** 对应 **UPDATE 语句**: - 用于更新数据库中的已有资源。 - 场景:更新资源的内容,例如修改用户信息、编辑文章内容、更新订单状态等。 ### 4.使用mybatis实现表关联查询逻辑,可以用框架原表,也可自建表,至少一项; 通过menuid将sys_menu系统菜单表和sys_role_menu表连接,再查询role对应的权限字段。 ```java @GetMapping(value = "/{id}") public ResponseEntity<List<String>> getPermissionById(@PathVariable("id") Long id) { List<String> permissions = permissionService.getPermissionById(id); if (permissions == null || permissions.isEmpty()) { return ResponseEntity.status(HttpStatus.NOT_FOUND) .body(null); // 404 Not Found } return ResponseEntity.ok(permissions); // 200 OK and return the permissions list } ``` ```xml <select id="getPermissionByRoleId" parameterType="java.lang.Long" resultType="java.lang.String"> select distinct m.perms from sys_menu m left join sys_role_menu rm on m.menu_id = rm.menu_id where m.status = '0' and rm.role_id = #{roleId} </select> ``` ### 5.了解框架缓存,哪些地方使用了缓存,实现了哪些功能; 1. 防重复提交时,将请求信息放到缓存,实现防重复提交或重复消费的功能。 2. 在接口限流时,将接口作为key,次数作为value存入缓存,实现接口限流。 3. 将token存入缓存,保存用户登录信息,实现无状态连接。 4. 将验证码放到缓存,对过期验证码进行无效处理。 5. 将密码错误次数存入缓存,防止密码撞库。 6. 将系统前端配置存入缓存,防止每次请求都需要从数据得到配置信息,减小数据库压力。 ### 6.了解框架中的token机制,并描述它的流程; 当有用户登录成功时,生成随机uuid,并使用赋值给loginUser对象。然后将uuid作为key,loginUser对象作为value存入缓存。再对uuid生成一个hashmap映射给jwt加盐加密,形成返回给前端的token。 在每一次请求的时候,从token中解析出uuid,再通过uuid从缓存中拿到用户信息即可,如uuid找不到缓存信息即缓存过期,就提示登录过期,或token无效。 <img src="/upload/blog/若依学习2/image-20241022161507137.png" alt="image-20241022161507137" style="zoom:50%;" /> ### 7.了解框架如何实现防重复提交操作的; 前端在请求拦截器里将每一次的post,put请求的url,data,time放到会话内存中,判断间隔时间是否小于1s是否为重复提交。详情请看第一题。 后端实现了@RepeatSubmit注解来防重复提交。在拦截器里检查接口是否使用@RepeatSubmit注解,使用模板方法模式,实现了防重复提交的流程。在防重复的提交的实现类中,将请求相关的信息存入缓存,通过请求提交的间隔时间判断是否为重复提交。redis缓存如下图:  ### 8.了解框架在何处实现的统一异常处理; 后端使用@RestControllerAdvice注解实现全局异常处理,捕获全局范围内的异常,以json格式的数据返回。 对后端产生的异常错误进行了封装,提供给前端直接调用,而不是返回错误信息。但这样后端返回的就永远是200状态码,将状态码封装到response里面,不符合restful风格。 因为@RestControllerAdvice切入是的控制器,所以无法对服务层和数据访问层的异常进行处理。 使用@RestControllerAdvice注解将类标识为全局异常处理器,作用范围包括所有@RestController标注的控制器,@RestControllerAdvice和@ExceptionHandler 的基于AOP实现,切入控制器方法的执行前后来捕获和处理异常。 例如: ```java /** * 权限校验异常 */ @ExceptionHandler(AccessDeniedException.class) public AjaxResult handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) { String requestURI = request.getRequestURI(); log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage()); return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权"); } ``` ### 9.前后端如何实现权限管理的。 后端通过springSecurity框架的@PreAuthorize注解和 PermissionService类实现细粒度的权限管理。在每一个接口上加上@PreAuthorize注解,然后PermissionService类从springSecurity上下文中获取登录用户的权限信息,来判断登录用户是否具有相应权限。@PreAuthorize基于AOP实现,切点为@PreAuthorize注解。 使用springSecurity配置对请求在filter层进行粗粒度的请求权限控制。 前端通过getroute接口得到用户的动态路由信息,getinfo接口得到用户的权限信息,在生成动态路由的时候对静态路由进行权限过滤,只生成用户有权限访问的动态路由。
评 论
目录