From 0e84f0934ef790748e8b4bda67853fdd00db01cb Mon Sep 17 00:00:00 2001 From: bunny <1319900154@qq.com> Date: Thu, 1 May 2025 13:44:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=A4=9A=E8=AF=AD?= =?UTF-8?q?=E8=A8=80=E3=80=81=E8=A7=92=E8=89=B2=E3=80=81=E6=9D=83=E9=99=90?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=B7=BB=E5=8A=A0=E5=9F=BA=E6=9C=AC=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新多语言注释信息;更新角色和权限改用事件驱动和并行流加异步 --- .../test/java/impl/I18nServiceImplTest.java | 32 ----- .../constant/SecurityConfigConstant.java | 9 +- .../service/PermissionCheckService.java | 4 +- .../email/AbstractSenderEmailTemplate.java | 2 +- .../email/ConcreteSenderEmailTemplate.java | 2 +- .../helper/i18n/I18nHelper.java} | 109 ++++++++++++------ .../configuration/impl/I18nServiceImpl.java | 21 ++-- .../system/helper/PermissionHelper.java} | 51 ++++---- .../system/helper/RouterHelper.java} | 40 +++++-- .../system/helper/UserLoginHelper.java | 4 +- .../system/helper/role/RoleHelper.java} | 54 ++++++--- .../system/helper/role/RoleUpdateHandler.java | 59 ++++++++++ .../system/helper/role/RoleUpdatedEvent.java | 17 +++ .../system/impl/PermissionServiceImpl.java | 10 +- .../impl/RolePermissionServiceImpl.java | 6 +- .../service/system/impl/RoleServiceImpl.java | 10 +- .../system/impl/RouterServiceImpl.java | 14 +-- .../system/impl/UserLoginServiceImpl.java | 25 +++- .../service/system/impl/UserServiceImpl.java | 24 +++- 19 files changed, 324 insertions(+), 169 deletions(-) rename service/src/main/java/cn/bunny/services/{utils => service/configuration/helper}/email/AbstractSenderEmailTemplate.java (98%) rename service/src/main/java/cn/bunny/services/{utils => service/configuration/helper}/email/ConcreteSenderEmailTemplate.java (96%) rename service/src/main/java/cn/bunny/services/{utils/i8n/I18nUtil.java => service/configuration/helper/i18n/I18nHelper.java} (58%) rename service/src/main/java/cn/bunny/services/{utils/system/PermissionUtil.java => service/system/helper/PermissionHelper.java} (67%) rename service/src/main/java/cn/bunny/services/{utils/system/RouterUtil.java => service/system/helper/RouterHelper.java} (82%) rename service/src/main/java/cn/bunny/services/{utils/system/RoleUtil.java => service/system/helper/role/RoleHelper.java} (61%) create mode 100644 service/src/main/java/cn/bunny/services/service/system/helper/role/RoleUpdateHandler.java create mode 100644 service/src/main/java/cn/bunny/services/service/system/helper/role/RoleUpdatedEvent.java diff --git a/auh-api/src/test/java/impl/I18nServiceImplTest.java b/auh-api/src/test/java/impl/I18nServiceImplTest.java index 636e569..64e1974 100644 --- a/auh-api/src/test/java/impl/I18nServiceImplTest.java +++ b/auh-api/src/test/java/impl/I18nServiceImplTest.java @@ -3,16 +3,12 @@ package impl; import cn.bunny.services.domain.common.model.dto.excel.I18nExcel; import cn.bunny.services.domain.system.i18n.entity.I18n; import cn.bunny.services.mapper.configuration.I18nMapper; -import cn.bunny.services.utils.i8n.I18nUtil; import com.alibaba.excel.EasyExcel; -import com.alibaba.fastjson2.JSON; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -21,34 +17,6 @@ import java.util.zip.ZipOutputStream; public class I18nServiceImplTest extends ServiceImpl { - @Test - void downloadI18n() { - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream); - - // 查找默认语言内容 - List i18nList = list(); - HashMap hashMap = I18nUtil.getMap(i18nList); - - hashMap.forEach((k, v) -> { - try { - ZipEntry zipEntry = new ZipEntry(k + ".json"); - zipOutputStream.putNextEntry(zipEntry); - - zipOutputStream.write(JSON.toJSONString(v).getBytes(StandardCharsets.UTF_8)); - zipOutputStream.closeEntry(); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - - try { - zipOutputStream.close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - @Test void downloadI18nByExcel() { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); diff --git a/auth-core/src/main/java/cn/bunny/services/domain/common/constant/SecurityConfigConstant.java b/auth-core/src/main/java/cn/bunny/services/domain/common/constant/SecurityConfigConstant.java index 7c19ad2..92f120e 100644 --- a/auth-core/src/main/java/cn/bunny/services/domain/common/constant/SecurityConfigConstant.java +++ b/auth-core/src/main/java/cn/bunny/services/domain/common/constant/SecurityConfigConstant.java @@ -4,14 +4,9 @@ import java.util.ArrayList; import java.util.List; public class SecurityConfigConstant { - public static String[] REQUEST_MATCHERS_PERMIT_ALL = { - "/", "/**.html", "/error", "/media.ico", "/favicon.ico", - "/webjars/**", "/v3/api-docs/**", "/swagger-ui/**", - "/*/*/noAuth/**", "/*/noAuth/**", "/noAuth/**", - "/*/i18n/getI18n" - }; - public static List PERMIT_ALL_LIST = new ArrayList<>() {{ + /* 可以放行的权限 */ + public static List PERMIT_ACCESS_LIST = new ArrayList<>() {{ add("admin"); }}; } diff --git a/service/src/main/java/cn/bunny/services/security/service/PermissionCheckService.java b/service/src/main/java/cn/bunny/services/security/service/PermissionCheckService.java index cf48504..2e397f8 100644 --- a/service/src/main/java/cn/bunny/services/security/service/PermissionCheckService.java +++ b/service/src/main/java/cn/bunny/services/security/service/PermissionCheckService.java @@ -6,7 +6,7 @@ import cn.bunny.services.domain.system.system.entity.Role; import cn.bunny.services.mapper.system.PermissionMapper; import cn.bunny.services.mapper.system.RoleMapper; import cn.bunny.services.security.config.WebSecurityConfig; -import cn.bunny.services.utils.system.RoleUtil; +import cn.bunny.services.service.system.helper.role.RoleHelper; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletRequest; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @@ -44,7 +44,7 @@ public class PermissionCheckService { List roleCodeList = roleList.stream().map(Role::getRoleCode).toList(); // 判断是否是管理员用户 - boolean checkedAdmin = RoleUtil.checkAdmin(roleCodeList); + boolean checkedAdmin = RoleHelper.checkAdmin(roleCodeList); if (checkedAdmin) return true; // 判断请求地址是否是登录之后才需要访问的,已经登录了不需要验证的 diff --git a/service/src/main/java/cn/bunny/services/utils/email/AbstractSenderEmailTemplate.java b/service/src/main/java/cn/bunny/services/service/configuration/helper/email/AbstractSenderEmailTemplate.java similarity index 98% rename from service/src/main/java/cn/bunny/services/utils/email/AbstractSenderEmailTemplate.java rename to service/src/main/java/cn/bunny/services/service/configuration/helper/email/AbstractSenderEmailTemplate.java index 923f927..0198bb7 100644 --- a/service/src/main/java/cn/bunny/services/utils/email/AbstractSenderEmailTemplate.java +++ b/service/src/main/java/cn/bunny/services/service/configuration/helper/email/AbstractSenderEmailTemplate.java @@ -1,4 +1,4 @@ -package cn.bunny.services.utils.email; +package cn.bunny.services.service.configuration.helper.email; import cn.bunny.services.domain.common.model.dto.email.EmailSend; import cn.bunny.services.domain.common.model.dto.email.EmailSendInit; diff --git a/service/src/main/java/cn/bunny/services/utils/email/ConcreteSenderEmailTemplate.java b/service/src/main/java/cn/bunny/services/service/configuration/helper/email/ConcreteSenderEmailTemplate.java similarity index 96% rename from service/src/main/java/cn/bunny/services/utils/email/ConcreteSenderEmailTemplate.java rename to service/src/main/java/cn/bunny/services/service/configuration/helper/email/ConcreteSenderEmailTemplate.java index 60b501e..ea0d5f0 100644 --- a/service/src/main/java/cn/bunny/services/utils/email/ConcreteSenderEmailTemplate.java +++ b/service/src/main/java/cn/bunny/services/service/configuration/helper/email/ConcreteSenderEmailTemplate.java @@ -1,4 +1,4 @@ -package cn.bunny.services.utils.email; +package cn.bunny.services.service.configuration.helper.email; import cn.bunny.services.domain.system.email.entity.EmailTemplate; import cn.bunny.services.mapper.configuration.EmailTemplateMapper; diff --git a/service/src/main/java/cn/bunny/services/utils/i8n/I18nUtil.java b/service/src/main/java/cn/bunny/services/service/configuration/helper/i18n/I18nHelper.java similarity index 58% rename from service/src/main/java/cn/bunny/services/utils/i8n/I18nUtil.java rename to service/src/main/java/cn/bunny/services/service/configuration/helper/i18n/I18nHelper.java index 78d5d2d..a6a052c 100644 --- a/service/src/main/java/cn/bunny/services/utils/i8n/I18nUtil.java +++ b/service/src/main/java/cn/bunny/services/service/configuration/helper/i18n/I18nHelper.java @@ -1,7 +1,8 @@ -package cn.bunny.services.utils.i8n; +package cn.bunny.services.service.configuration.helper.i18n; import cn.bunny.services.domain.common.model.dto.excel.I18nExcel; import cn.bunny.services.domain.system.i18n.entity.I18n; +import cn.bunny.services.service.configuration.impl.I18nServiceImpl; import com.alibaba.excel.EasyExcel; import com.alibaba.fastjson2.JSON; import org.jetbrains.annotations.NotNull; @@ -16,46 +17,20 @@ import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -public class I18nUtil { - @NotNull - public static HashMap getMap(@NotNull List i18nList) { - // 整理集合 - Map> map = i18nList.stream() - .collect(Collectors.groupingBy( - I18n::getTypeName, - Collectors.toMap(I18n::getKeyName, I18n::getTranslation))); - - // 返回集合 - return new HashMap<>(map); - } - +public class I18nHelper { /** - * 使用zip写入json + * 将国际化资源列表写入Excel并打包到ZIP输出流 * - * @param i18nList i18nList - * @param zipOutputStream zipOutputStream - */ - public static void writeJson(List i18nList, ZipOutputStream zipOutputStream) { - HashMap hashMap = getMap(i18nList); - - hashMap.forEach((k, v) -> { - try { - ZipEntry zipEntry = new ZipEntry(k + ".json"); - zipOutputStream.putNextEntry(zipEntry); - - zipOutputStream.write(JSON.toJSONString(v).getBytes(StandardCharsets.UTF_8)); - zipOutputStream.closeEntry(); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - } - - /** - * 使用zip写入excel + *

处理流程:

+ *
    + *
  1. 按资源类型(typeName)分组 --- 多语言的类型,英文?中文?
  2. + *
  3. 每组数据生成单独多语言 key的Excel工作表
  4. + *
  5. 将Excel文件写入ZIP输出流
  6. + *
* - * @param i18nList i18nList - * @param zipOutputStream zipOutputStream + * @param i18nList 国际化资源列表,包含key-value对和类型信息 + * @param zipOutputStream ZIP输出流,用于写入打包后的Excel文件 + * @throws RuntimeException 当IO操作失败时抛出 */ public static void writeExcel(List i18nList, ZipOutputStream zipOutputStream) { Map> hashMap = i18nList.stream() @@ -86,4 +61,62 @@ public class I18nUtil { } }); } + + /** + * 将国际化资源列表写入JSON并打包到ZIP输出流 + * + *

处理流程:

+ *
    + *
  1. 按资源类型(typeName)分组 --- 多语言的类型,英文?中文?
  2. + *
  3. 每组数据生成单独多语言 key的Excel工作表
  4. + *
  5. 将JSON文件写入ZIP输出流
  6. + *
+ * + * @param i18nList 国际化资源列表 + * @param zipOutputStream ZIP输出流 + * @throws RuntimeException 当IO操作失败时抛出 + */ + public static void writeJson(List i18nList, ZipOutputStream zipOutputStream) { + HashMap hashMap = getMapByI18nList(i18nList); + + hashMap.forEach((k, v) -> { + try { + ZipEntry zipEntry = new ZipEntry(k + ".json"); + zipOutputStream.putNextEntry(zipEntry); + + zipOutputStream.write(JSON.toJSONString(v).getBytes(StandardCharsets.UTF_8)); + zipOutputStream.closeEntry(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + + /** + * 将国际化资源列表转换为结构化Map + * + *

转换规则:

+ *
    + *
  • 外层Key: 资源类型(typeName)
  • + *
  • 内层Key: 资源键名(keyName)
  • + *
  • 值: 翻译文本(translation)
  • + *
+ *

详细结构和结果示例看前端传递的 {@link I18nServiceImpl#getI18nMap} 控制器

+ *

/api/i18n/public

+ * + * @param i18nList 国际化资源列表 + * @return 结构化Map {typeName: {keyName: translation}} + * @throws IllegalArgumentException 当参数为null时抛出 + */ + @NotNull + public static HashMap getMapByI18nList(@NotNull List i18nList) { + // 整理集合 + Map> map = i18nList.stream() + .collect(Collectors.groupingBy( + I18n::getTypeName, + Collectors.toMap(I18n::getKeyName, I18n::getTranslation))); + + // 返回集合 + return new HashMap<>(map); + } } diff --git a/service/src/main/java/cn/bunny/services/service/configuration/impl/I18nServiceImpl.java b/service/src/main/java/cn/bunny/services/service/configuration/impl/I18nServiceImpl.java index 562333a..babb94e 100644 --- a/service/src/main/java/cn/bunny/services/service/configuration/impl/I18nServiceImpl.java +++ b/service/src/main/java/cn/bunny/services/service/configuration/impl/I18nServiceImpl.java @@ -1,8 +1,8 @@ package cn.bunny.services.service.configuration.impl; import cn.bunny.services.domain.common.constant.FileType; -import cn.bunny.services.domain.common.model.entity.BaseEntity; import cn.bunny.services.domain.common.model.dto.excel.I18nExcel; +import cn.bunny.services.domain.common.model.entity.BaseEntity; import cn.bunny.services.domain.common.model.vo.result.PageResult; import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum; import cn.bunny.services.domain.system.i18n.dto.I18nAddDto; @@ -17,8 +17,8 @@ import cn.bunny.services.exception.AuthCustomerException; import cn.bunny.services.mapper.configuration.I18nMapper; import cn.bunny.services.mapper.configuration.I18nTypeMapper; import cn.bunny.services.service.configuration.I18nService; +import cn.bunny.services.service.configuration.helper.i18n.I18nHelper; import cn.bunny.services.utils.FileUtil; -import cn.bunny.services.utils.i8n.I18nUtil; import com.alibaba.excel.EasyExcel; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.TypeReference; @@ -76,7 +76,7 @@ public class I18nServiceImpl extends ServiceImpl implements I1 I18nType i18nType = i18nTypeMapper.selectOne(Wrappers.lambdaQuery().eq(I18nType::getIsDefault, true)); List i18nList = list(); - HashMap hashMap = I18nUtil.getMap(i18nList); + HashMap hashMap = I18nHelper.getMapByI18nList(i18nList); hashMap.put("local", Objects.requireNonNull(i18nType.getTypeName(), "zh")); return hashMap; @@ -170,22 +170,21 @@ public class I18nServiceImpl extends ServiceImpl implements I1 // 类型是Excel写入Excel if (type.equals(FileType.EXCEL)) { - I18nUtil.writeExcel(i18nList, zipOutputStream); + I18nHelper.writeExcel(i18nList, zipOutputStream); } // 其他格式写入JSON else { - I18nUtil.writeJson(i18nList, zipOutputStream); + I18nHelper.writeJson(i18nList, zipOutputStream); } + // 设置响应头 + HttpHeaders headers = FileUtil.buildHttpHeadersByBinary("i18n-configuration.zip"); + + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); + return new ResponseEntity<>(byteArrayInputStream.readAllBytes(), headers, HttpStatus.OK); } catch (IOException e) { throw new RuntimeException(e); } - - // 设置响应头 - HttpHeaders headers = FileUtil.buildHttpHeadersByBinary("i18n-configuration.zip"); - - ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); - return new ResponseEntity<>(byteArrayInputStream.readAllBytes(), headers, HttpStatus.OK); } /** diff --git a/service/src/main/java/cn/bunny/services/utils/system/PermissionUtil.java b/service/src/main/java/cn/bunny/services/service/system/helper/PermissionHelper.java similarity index 67% rename from service/src/main/java/cn/bunny/services/utils/system/PermissionUtil.java rename to service/src/main/java/cn/bunny/services/service/system/helper/PermissionHelper.java index 132e1a9..86708e4 100644 --- a/service/src/main/java/cn/bunny/services/utils/system/PermissionUtil.java +++ b/service/src/main/java/cn/bunny/services/service/system/helper/PermissionHelper.java @@ -1,4 +1,4 @@ -package cn.bunny.services.utils.system; +package cn.bunny.services.service.system.helper; import cn.bunny.services.domain.common.model.dto.excel.PermissionExcel; import com.alibaba.excel.EasyExcel; @@ -11,12 +11,20 @@ import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; -public class PermissionUtil { +/** + * 权限数据处理工具类 + * + *

提供权限数据的树形结构处理、扁平化处理以及导出功能

+ */ +public class PermissionHelper { + /** - * 将属性结构扁平化 + * 将树形结构权限数据扁平化为列表 * - * @param list 属性结构 - * @return 扁平化数组 + *

使用递归处理树形结构

+ * + * @param list 树形结构的权限列表,每个节点可能包含children子节点 + * @return 扁平化后的权限列表( */ public static List flattenTree(List list) { List result = new ArrayList<>(); @@ -32,10 +40,12 @@ public class PermissionUtil { } /** - * 设置子集 + * 递归设置子节点(内部方法) * - * @param parent 父级节点 - * @param list 要构建的列表 + *

为父节点查找并设置所有子节点,递归处理子节点的子节点

+ * + * @param parent 当前父节点 + * @param list 完整的权限数据列表 */ private static void setChildren(PermissionExcel parent, List list) { List children = list.stream() @@ -51,12 +61,13 @@ public class PermissionUtil { } } - /** - * 构建属性结构 + * 构建权限树形结构 * - * @param list 要构建的列表 - * @return 构建完成的列表 + *

从扁平列表中构建树形结构,根节点的判断条件为parentId为null或0

+ * + * @param list 扁平化的权限数据列表 + * @return 构建完成的树形结构列表(只包含根节点) */ public static List buildTree(List list) { List permissionExcels = list.stream() @@ -70,11 +81,11 @@ public class PermissionUtil { } /** - * 写入JSON + * 将权限数据写入JSON格式到ZIP压缩包 * - * @param list 写入的列表 - * @param zipOutputStream zip输出流 - * @param zipName zip文件名 + * @param list 要导出的权限数据列表 + * @param zipOutputStream ZIP输出流 + * @param zipName 在ZIP包中的文件名 */ public static void writeJson(List list, ZipOutputStream zipOutputStream, String zipName) { try { @@ -88,11 +99,11 @@ public class PermissionUtil { } /** - * 写入JSON + * 将权限数据写入Excel格式到ZIP压缩包 * - * @param list 写入的列表 - * @param zipOutputStream zip输出流 - * @param zipName zip文件名 + * @param list 要导出的权限数据列表 + * @param zipOutputStream ZIP输出流 + * @param zipName 在ZIP包中的文件名(需包含.xlsx后缀) */ public static void writExcel(List list, ZipOutputStream zipOutputStream, String zipName) { try { diff --git a/service/src/main/java/cn/bunny/services/utils/system/RouterUtil.java b/service/src/main/java/cn/bunny/services/service/system/helper/RouterHelper.java similarity index 82% rename from service/src/main/java/cn/bunny/services/utils/system/RouterUtil.java rename to service/src/main/java/cn/bunny/services/service/system/helper/RouterHelper.java index 799e414..60e2bf1 100644 --- a/service/src/main/java/cn/bunny/services/utils/system/RouterUtil.java +++ b/service/src/main/java/cn/bunny/services/service/system/helper/RouterHelper.java @@ -1,13 +1,14 @@ -package cn.bunny.services.utils.system; +package cn.bunny.services.service.system.helper; +import cn.bunny.services.context.BaseContext; import cn.bunny.services.domain.system.system.entity.RouterRole; import cn.bunny.services.domain.system.system.entity.router.Router; import cn.bunny.services.domain.system.system.entity.router.RouterMeta; import cn.bunny.services.domain.system.system.views.ViewRolePermission; import cn.bunny.services.domain.system.system.views.ViewRouterRole; import cn.bunny.services.domain.system.system.vo.router.WebUserRouterVo; -import cn.bunny.services.context.BaseContext; import cn.bunny.services.service.system.RouterRoleService; +import cn.bunny.services.service.system.helper.role.RoleHelper; import com.alibaba.fastjson2.JSON; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; @@ -20,7 +21,7 @@ import java.util.*; @Slf4j @Component -public class RouterUtil { +public class RouterHelper { @Resource private RouterRoleService routerRoleService; @@ -44,10 +45,12 @@ public class RouterUtil { } /** - * 查询新的路由权限和角色 + * 保存路由角色关联关系 * - * @param meta RouterMeta - * @param id 路由id + *

将路由的权限角色配置保存到数据库

+ * + * @param meta 路由元数据,包含角色配置信息 + * @param id 路由ID */ public void insertRouterRoleAndPermission(RouterMeta meta, Long id) { List roles = meta.getRoles(); @@ -63,18 +66,31 @@ public class RouterUtil { } /** - * 整理web用户所能看到的路由列表 + * 构建前端可访问的路由列表 * - * @param routerList 所有的路由列表 - * @param routerRoleList 路由和角色列表 - * @param rolePermissionList 角色和权限列表 - * @return web用户所能看到的路由列表 + *

处理流程:

+ *
    + *
  1. 检查当前用户是否为管理员(拥有全部权限)
  2. + *
  3. 转换路由基础信息
  4. + *
  5. 处理路由元数据(meta)
  6. + *
  7. 设置路由关联的角色信息
  8. + *
  9. 设置路由关联的权限信息
  10. + *
  11. 按rank排序返回
  12. + *
+ * + * 路由结构参考这个文档 + * + * + * @param routerList 所有路由列表 + * @param routerRoleList 路由-角色关联Map(key: 路由ID, value: 角色列表) + * @param rolePermissionList 角色-权限关联Map(key: 角色ID, value: 权限列表) + * @return 前端可访问的路由列表(包含完整的权限信息) */ @NotNull public List getWebUserRouterVos(List routerList, Map> routerRoleList, Map> rolePermissionList) { // 检查当前是否是 admin 用户 List roles = BaseContext.getLoginVo().getRoles(); - List allAuths = !RoleUtil.checkAdmin(roles) ? new ArrayList<>() : List.of("*:*:*", "*:*", "*", "admin"); + List allAuths = !RoleHelper.checkAdmin(roles) ? new ArrayList<>() : List.of("*:*:*", "*:*", "*", "admin"); // 查询路由所有数据,整理前端需要的路和、角色、权限 return routerList.stream().map(view -> { diff --git a/service/src/main/java/cn/bunny/services/service/system/helper/UserLoginHelper.java b/service/src/main/java/cn/bunny/services/service/system/helper/UserLoginHelper.java index dc7816b..b1b2e42 100644 --- a/service/src/main/java/cn/bunny/services/service/system/helper/UserLoginHelper.java +++ b/service/src/main/java/cn/bunny/services/service/system/helper/UserLoginHelper.java @@ -13,9 +13,9 @@ import cn.bunny.services.mapper.system.PermissionMapper; import cn.bunny.services.mapper.system.RoleMapper; import cn.bunny.services.mapper.system.UserMapper; import cn.bunny.services.minio.MinioHelper; +import cn.bunny.services.service.system.helper.role.RoleHelper; import cn.bunny.services.utils.IpUtil; import cn.bunny.services.utils.JwtTokenUtil; -import cn.bunny.services.utils.system.RoleUtil; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; @@ -94,7 +94,7 @@ public class UserLoginHelper { // 判断是否是 admin 如果是admin 赋予所有权限 List permissions = new ArrayList<>(); - boolean isAdmin = RoleUtil.checkAdmin(roles, permissions, user); + boolean isAdmin = RoleHelper.checkAdmin(roles, permissions, user); if (!isAdmin) { permissions = permissionMapper.selectListByUserId(userId).stream() .map(Permission::getPowerCode) diff --git a/service/src/main/java/cn/bunny/services/utils/system/RoleUtil.java b/service/src/main/java/cn/bunny/services/service/system/helper/role/RoleHelper.java similarity index 61% rename from service/src/main/java/cn/bunny/services/utils/system/RoleUtil.java rename to service/src/main/java/cn/bunny/services/service/system/helper/role/RoleHelper.java index 0e17865..719d974 100644 --- a/service/src/main/java/cn/bunny/services/utils/system/RoleUtil.java +++ b/service/src/main/java/cn/bunny/services/service/system/helper/role/RoleHelper.java @@ -1,4 +1,4 @@ -package cn.bunny.services.utils.system; +package cn.bunny.services.service.system.helper.role; import cn.bunny.services.context.BaseContext; import cn.bunny.services.domain.common.constant.RedisUserConstant; @@ -6,7 +6,6 @@ import cn.bunny.services.domain.common.constant.SecurityConfigConstant; import cn.bunny.services.domain.system.system.entity.AdminUser; import cn.bunny.services.mapper.system.UserMapper; import cn.bunny.services.service.system.helper.UserLoginHelper; -import com.baomidou.mybatisplus.core.toolkit.Wrappers; import jakarta.annotation.Resource; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @@ -14,7 +13,7 @@ import org.springframework.stereotype.Component; import java.util.List; @Component -public class RoleUtil { +public class RoleHelper { @Resource private RedisTemplate redisTemplate; @@ -73,36 +72,53 @@ public class RoleUtil { */ public static boolean checkAdmin(List roleList) { // 可以放行的权限 - List permitAllList = SecurityConfigConstant.PERMIT_ALL_LIST; + List permissionList = SecurityConfigConstant.PERMIT_ACCESS_LIST; // 判断是否是超级管理员 if (BaseContext.getUserId().equals(1L)) return true; // 判断是否是 admin - return roleList.stream().anyMatch(permitAllList::contains); + return roleList.stream().anyMatch(permissionList::contains); } /** - * 批量更新Redis中用户信息 + * 批量更新Redis中用户权限信息 * - * @param userIds 用户Id列表 + *

使用场景:当用户角色或权限变更时,同步更新Redis中的用户权限数据

+ * + *

实现策略

+ *
    + *
  1. 主动更新(当前实现):重新构建用户权限信息并更新Redis缓存
  2. + *
  3. 强制下线:删除用户登录态,强制重新认证获取最新权限
  4. + *
+ * + *

技术实现

+ *
    + *
  • 采用Spring事件驱动机制触发更新
  • + *
  • 使用并行流(parallelStream)提高批量处理效率
  • + *
  • 仅更新Redis中存在登录态的用户
  • + *
+ * + * @param userIds 需要更新的用户ID集合 + * (仅处理集合中存在的有效用户) + * @see RedisUserConstant Redis键前缀常量 + * @see UserLoginHelper#buildLoginUserVo 用户登录信息构建方法 */ public void updateUserRedisInfo(List userIds) { if (userIds.isEmpty()) return; - // 根据Id查找所有用户 - List adminUsers = userMapper.selectList(Wrappers.lambdaQuery().in(AdminUser::getId, userIds)); + // 批量查询用户 + List adminUsers = userMapper.selectBatchIds(userIds); - // 用户为空时不更新Redis的key - if (adminUsers.isEmpty()) return; + // 并行处理用户更新 + adminUsers.parallelStream() + .filter(user -> redisTemplate.hasKey(RedisUserConstant.getAdminLoginInfoPrefix(user.getUsername()))) + .forEach(user -> { + // 策略1: 更新用户权限信息 + userloginHelper.buildLoginUserVo(user, RedisUserConstant.REDIS_EXPIRATION_TIME); - // 更新Redis中用户信息 - adminUsers.stream() - .filter(user -> { - String adminLoginInfoPrefix = RedisUserConstant.getAdminLoginInfoPrefix(user.getUsername()); - Object object = redisTemplate.opsForValue().get(adminLoginInfoPrefix); - return object != null; - }) - .forEach(user -> userloginHelper.buildLoginUserVo(user, RedisUserConstant.REDIS_EXPIRATION_TIME)); + // 或者策略2: 强制用户下线 + // redisTemplate.delete(RedisUserConstant.getAdminLoginInfoPrefix(user.getUsername())); + }); } } diff --git a/service/src/main/java/cn/bunny/services/service/system/helper/role/RoleUpdateHandler.java b/service/src/main/java/cn/bunny/services/service/system/helper/role/RoleUpdateHandler.java new file mode 100644 index 0000000..753e134 --- /dev/null +++ b/service/src/main/java/cn/bunny/services/service/system/helper/role/RoleUpdateHandler.java @@ -0,0 +1,59 @@ +package cn.bunny.services.service.system.helper.role; + +import cn.bunny.services.domain.system.system.entity.UserRole; +import cn.bunny.services.mapper.system.UserRoleMapper; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import jakarta.annotation.Resource; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 角色更新事件处理器 + * + *

职责说明:监听并处理角色变更事件,同步更新关联用户的权限信息

+ * + *

处理流程

+ *
    + *
  1. 监听{@code RoleUpdatedEvent}事件
  2. + *
  3. 查询关联该角色的所有用户ID
  4. + *
  5. 通过{@code RoleHelper}批量更新用户Redis缓存
  6. + *
+ * + *

注意事项

+ *
    + *
  • 使用异步处理(@{@link Async})避免阻塞主线程
  • + *
  • 仅处理直接关联的用户,不包含通过用户组等间接关联的情况
  • + *
  • 更新操作采用批量处理提高效率
  • + *
+ */ +@Component +public class RoleUpdateHandler { + @Resource + private UserRoleMapper userRoleMapper; + + @Resource + private RoleHelper roleHelper; + + /** + * 处理角色更新事件 + * + * @param event 角色更新事件,包含变更的角色ID + * @see RoleUpdatedEvent 角色更新事件定义 + * @see RoleHelper#updateUserRedisInfo 用户缓存更新方法 + */ + @Async + @EventListener + public void handleRoleUpdatedEvent(RoleUpdatedEvent event) { + // 查询当前用户角色中角色id + LambdaQueryWrapper lambdaQueryWrapper = Wrappers.lambdaQuery().eq(UserRole::getRoleId, event.getRoleId()); + + // 批量查询关联用户ID + List userRoles = userRoleMapper.selectList(lambdaQueryWrapper); + List userIds = userRoles.stream().map(UserRole::getUserId).toList(); + roleHelper.updateUserRedisInfo(userIds); + } +} \ No newline at end of file diff --git a/service/src/main/java/cn/bunny/services/service/system/helper/role/RoleUpdatedEvent.java b/service/src/main/java/cn/bunny/services/service/system/helper/role/RoleUpdatedEvent.java new file mode 100644 index 0000000..bfbfe13 --- /dev/null +++ b/service/src/main/java/cn/bunny/services/service/system/helper/role/RoleUpdatedEvent.java @@ -0,0 +1,17 @@ +package cn.bunny.services.service.system.helper.role; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.context.ApplicationEvent; + +// 角色更新事件 +@Getter +@Setter +public class RoleUpdatedEvent extends ApplicationEvent { + private final Long roleId; + + public RoleUpdatedEvent(Object source, Long roleId) { + super(source); + this.roleId = roleId; + } +} \ No newline at end of file diff --git a/service/src/main/java/cn/bunny/services/service/system/impl/PermissionServiceImpl.java b/service/src/main/java/cn/bunny/services/service/system/impl/PermissionServiceImpl.java index d96afa7..8d470db 100644 --- a/service/src/main/java/cn/bunny/services/service/system/impl/PermissionServiceImpl.java +++ b/service/src/main/java/cn/bunny/services/service/system/impl/PermissionServiceImpl.java @@ -15,8 +15,8 @@ import cn.bunny.services.exception.AuthCustomerException; import cn.bunny.services.mapper.system.PermissionMapper; import cn.bunny.services.mapper.system.RolePermissionMapper; import cn.bunny.services.service.system.PermissionService; +import cn.bunny.services.service.system.helper.PermissionHelper; import cn.bunny.services.utils.FileUtil; -import cn.bunny.services.utils.system.PermissionUtil; import com.alibaba.excel.EasyExcel; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.TypeReference; @@ -187,7 +187,7 @@ public class PermissionServiceImpl extends ServiceImpl buildTree = PermissionUtil.buildTree(permissionExcelList); + List buildTree = PermissionHelper.buildTree(permissionExcelList); // 创建btye输出流 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); @@ -197,9 +197,9 @@ public class PermissionServiceImpl extends ServiceImpl list = JSON.parseObject(json, new TypeReference<>() { }); // 格式化数据,保存到数据库 - List flattenedTree = PermissionUtil.flattenTree(list); + List flattenedTree = PermissionHelper.flattenTree(list); List permissionList = flattenedTree.stream().map(permissionExcel -> { Permission permission = new Permission(); BeanUtils.copyProperties(permissionExcel, permission); diff --git a/service/src/main/java/cn/bunny/services/service/system/impl/RolePermissionServiceImpl.java b/service/src/main/java/cn/bunny/services/service/system/impl/RolePermissionServiceImpl.java index bc78233..59f3ebe 100644 --- a/service/src/main/java/cn/bunny/services/service/system/impl/RolePermissionServiceImpl.java +++ b/service/src/main/java/cn/bunny/services/service/system/impl/RolePermissionServiceImpl.java @@ -8,7 +8,7 @@ import cn.bunny.services.mapper.system.RolePermissionMapper; import cn.bunny.services.mapper.system.UserMapper; import cn.bunny.services.mapper.system.UserRoleMapper; import cn.bunny.services.service.system.RolePermissionService; -import cn.bunny.services.utils.system.RoleUtil; +import cn.bunny.services.service.system.helper.role.RoleHelper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import jakarta.annotation.Resource; @@ -33,7 +33,7 @@ public class RolePermissionServiceImpl extends ServiceImpl userIds = adminUsers.stream().map(AdminUser::getId).toList(); - roleUtil.updateUserRedisInfo(userIds); + roleHelper.updateUserRedisInfo(userIds); } } diff --git a/service/src/main/java/cn/bunny/services/service/system/impl/RoleServiceImpl.java b/service/src/main/java/cn/bunny/services/service/system/impl/RoleServiceImpl.java index db8d726..236c7a4 100644 --- a/service/src/main/java/cn/bunny/services/service/system/impl/RoleServiceImpl.java +++ b/service/src/main/java/cn/bunny/services/service/system/impl/RoleServiceImpl.java @@ -16,8 +16,8 @@ import cn.bunny.services.mapper.system.RolePermissionMapper; import cn.bunny.services.mapper.system.RouterRoleMapper; import cn.bunny.services.mapper.system.UserRoleMapper; import cn.bunny.services.service.system.RoleService; +import cn.bunny.services.service.system.helper.role.RoleUpdatedEvent; import cn.bunny.services.utils.FileUtil; -import cn.bunny.services.utils.system.RoleUtil; import com.alibaba.excel.EasyExcel; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.core.toolkit.Wrappers; @@ -28,6 +28,7 @@ import jakarta.validation.Valid; import org.springframework.beans.BeanUtils; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -67,7 +68,7 @@ public class RoleServiceImpl extends ServiceImpl implements Ro private RouterRoleMapper routerRoleMapper; @Resource - private RoleUtil roleUtil; + private ApplicationEventPublisher eventPublisher; /** * * 角色 服务实现类 @@ -203,7 +204,10 @@ public class RoleServiceImpl extends ServiceImpl implements Ro // 找到所有和当前更新角色相同的用户,并更新Redis中用户信息 List userIds = userRoleMapper.selectList(Wrappers.lambdaQuery().eq(UserRole::getRoleId, dto.getId())) .stream().map(UserRole::getUserId).toList(); - roleUtil.updateUserRedisInfo(userIds); + // TODO 1 + // roleUtil.updateUserRedisInfo(userIds); + // 发布角色更新事件 + eventPublisher.publishEvent(new RoleUpdatedEvent(this, dto.getId())); } diff --git a/service/src/main/java/cn/bunny/services/service/system/impl/RouterServiceImpl.java b/service/src/main/java/cn/bunny/services/service/system/impl/RouterServiceImpl.java index b921ffc..96d74e5 100644 --- a/service/src/main/java/cn/bunny/services/service/system/impl/RouterServiceImpl.java +++ b/service/src/main/java/cn/bunny/services/service/system/impl/RouterServiceImpl.java @@ -1,5 +1,6 @@ package cn.bunny.services.service.system.impl; +import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum; import cn.bunny.services.domain.system.system.dto.router.RouterAddDto; import cn.bunny.services.domain.system.system.dto.router.RouterUpdateDto; import cn.bunny.services.domain.system.system.entity.router.Router; @@ -10,13 +11,12 @@ import cn.bunny.services.domain.system.system.views.ViewRouterRole; import cn.bunny.services.domain.system.system.vo.router.RouterManageVo; import cn.bunny.services.domain.system.system.vo.router.RouterVo; import cn.bunny.services.domain.system.system.vo.router.WebUserRouterVo; -import cn.bunny.services.domain.common.model.vo.result.ResultCodeEnum; import cn.bunny.services.exception.AuthCustomerException; import cn.bunny.services.mapper.system.RolePermissionMapper; import cn.bunny.services.mapper.system.RouterMapper; import cn.bunny.services.mapper.system.RouterRoleMapper; import cn.bunny.services.service.system.RouterService; -import cn.bunny.services.utils.system.RouterUtil; +import cn.bunny.services.service.system.helper.RouterHelper; import com.alibaba.fastjson2.JSON; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -48,7 +48,7 @@ public class RouterServiceImpl extends ServiceImpl impleme private RouterRoleMapper routerRoleMapper; @Resource - private RouterUtil routerUtil; + private RouterHelper routerHelper; @Resource private RolePermissionMapper rolePermissionMapper; @@ -75,13 +75,13 @@ public class RouterServiceImpl extends ServiceImpl impleme .collect(Collectors.groupingBy(ViewRolePermission::getRoleId, Collectors.toList())); // 整理web用户所能看到的路由列表,并检查当前用户是否是admin - List webUserRouterVoList = routerUtil.getWebUserRouterVos(routerList, routerRoleList, rolePermissionList); + List webUserRouterVoList = routerHelper.getWebUserRouterVos(routerList, routerRoleList, rolePermissionList); // 添加 admin 管理路由权限 webUserRouterVoList.forEach(routerVo -> { // 递归添加路由节点 if (routerVo.getParentId() == 0) { - routerVo.setChildren(routerUtil.buildTreeSetChildren(routerVo.getId(), webUserRouterVoList)); + routerVo.setChildren(routerHelper.buildTreeSetChildren(routerVo.getId(), webUserRouterVoList)); voList.add(routerVo); } }); @@ -141,7 +141,7 @@ public class RouterServiceImpl extends ServiceImpl impleme // 将数据提出role 和 power 存储到数据库 Long id = router.getId(); - routerUtil.insertRouterRoleAndPermission(meta, id); + routerHelper.insertRouterRoleAndPermission(meta, id); // 添加路由 save(router); @@ -169,7 +169,7 @@ public class RouterServiceImpl extends ServiceImpl impleme routerRoleMapper.deleteBatchIdsByRouterIds(List.of(id)); // 将数据提出role 和 power 存储到数据库 - routerUtil.insertRouterRoleAndPermission(meta, id); + routerHelper.insertRouterRoleAndPermission(meta, id); // 更新路由信息 updateById(router); diff --git a/service/src/main/java/cn/bunny/services/service/system/impl/UserLoginServiceImpl.java b/service/src/main/java/cn/bunny/services/service/system/impl/UserLoginServiceImpl.java index 9b2b6b9..a7ba8ab 100644 --- a/service/src/main/java/cn/bunny/services/service/system/impl/UserLoginServiceImpl.java +++ b/service/src/main/java/cn/bunny/services/service/system/impl/UserLoginServiceImpl.java @@ -15,6 +15,7 @@ import cn.bunny.services.domain.system.system.vo.user.RefreshTokenVo; import cn.bunny.services.exception.AuthCustomerException; import cn.bunny.services.mapper.configuration.EmailTemplateMapper; import cn.bunny.services.mapper.system.UserMapper; +import cn.bunny.services.service.configuration.helper.email.ConcreteSenderEmailTemplate; import cn.bunny.services.service.system.UserLoginService; import cn.bunny.services.service.system.helper.UserLoginHelper; import cn.bunny.services.service.system.helper.login.DefaultLoginStrategy; @@ -23,7 +24,6 @@ import cn.bunny.services.service.system.helper.login.LoginContext; import cn.bunny.services.service.system.helper.login.LoginStrategy; import cn.bunny.services.utils.IpUtil; import cn.bunny.services.utils.JwtTokenUtil; -import cn.bunny.services.utils.email.ConcreteSenderEmailTemplate; import cn.hutool.captcha.CaptchaUtil; import cn.hutool.captcha.CircleCaptcha; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -103,9 +103,25 @@ public class UserLoginServiceImpl extends ServiceImpl imp } /** - * 登录发送邮件验证码 + * 发送登录邮件验证码 * - * @param email 邮箱 + *

完整处理流程:

+ *
    + *
  1. 查询模板:从数据库获取默认的验证码邮件模板
  2. + *
  3. 生成验证码:创建4位数字验证码
  4. + *
  5. 模板处理:替换模板中的动态变量(系统名称、验证码等)
  6. + *
  7. 发送邮件:通过邮件服务发送处理后的模板
  8. + *
  9. 缓存验证码:将验证码存入Redis 有效期 xxx
  10. + *
+ * + * @param email 接收邮箱地址(不可为空) + *
    + *
  • 未找到默认邮件模板
  • + *
  • 邮件发送失败
  • + *
  • Redis操作异常
  • + *
+ * @see EmailTemplateEnums#VERIFICATION_CODE 验证码模板类型枚举 + * @see RedisUserConstant Redis键和过期时间常量 */ @Override public void sendLoginEmail(@NotNull String email) { @@ -131,7 +147,8 @@ public class UserLoginServiceImpl extends ServiceImpl imp concreteSenderEmailTemplate.sendEmailTemplate(email, emailTemplate, hashMap); // 在Redis中存储验证码 - redisTemplate.opsForValue().set(RedisUserConstant.getAdminUserEmailCodePrefix(email), emailCode, RedisUserConstant.REDIS_EXPIRATION_TIME, TimeUnit.MINUTES); + String emailCodePrefix = RedisUserConstant.getAdminUserEmailCodePrefix(email); + redisTemplate.opsForValue().set(emailCodePrefix, emailCode, RedisUserConstant.REDIS_EXPIRATION_TIME, TimeUnit.MINUTES); } /** diff --git a/service/src/main/java/cn/bunny/services/service/system/impl/UserServiceImpl.java b/service/src/main/java/cn/bunny/services/service/system/impl/UserServiceImpl.java index 072da50..a3c82da 100644 --- a/service/src/main/java/cn/bunny/services/service/system/impl/UserServiceImpl.java +++ b/service/src/main/java/cn/bunny/services/service/system/impl/UserServiceImpl.java @@ -102,9 +102,28 @@ public class UserServiceImpl extends ServiceImpl implemen } /** - * 强制退出 + * 管理员强制用户下线 * - * @param id 用户id + *

功能说明:管理员强制指定用户退出登录状态,并记录操作日志

+ * + *

处理流程

+ *
    + *
  1. 参数校验:检查用户ID是否为空
  2. + *
  3. 查询用户信息:根据ID获取用户实体
  4. + *
  5. 记录操作日志:保存强制下线记录到用户登录日志表
  6. + *
  7. 清除登录状态:删除Redis中的用户登录信息
  8. + *
+ * + *

注意事项

+ *
    + *
  • 会中断用户当前会话,无需用户确认
  • + *
  • 操作会记录到用户登录日志,用于审计追踪
  • + *
  • Redis键使用用户名作为唯一标识
  • + *
+ * + * @param id 用户ID(不可为空) + * @see RedisUserConstant#getAdminLoginInfoPrefix Redis键生成规则 + * @see UserConstant#FORCE_LOGOUT 强制下线类型常量 */ @Override public void forcedOfflineByAdmin(Long id) { @@ -117,6 +136,7 @@ public class UserServiceImpl extends ServiceImpl implemen // 将用户登录保存在用户登录日志表中 UserLoginLog userLoginLog = new UserLoginLog(); BeanUtils.copyProperties(adminUser, userLoginLog); + userLoginLog.setId(null); userLoginLog.setUserId(adminUser.getId()); userLoginLog.setType(UserConstant.FORCE_LOGOUT); userLoginLogMapper.insert(userLoginLog);