refactor(common-base-web): Extract import and export functions into separate classes

This commit is contained in:
sol 2025-09-16 10:40:29 +08:00
parent ea20afefc2
commit 7408258faa
6 changed files with 264 additions and 215 deletions

View file

@ -6,16 +6,11 @@ import com.bgasol.common.core.base.dto.BaseUpdateDto;
import com.bgasol.common.core.base.entity.BaseEntity;
import com.bgasol.common.core.base.service.BaseService;
import com.bgasol.common.core.base.vo.BaseVo;
import com.bgasol.common.core.base.vo.ImportResult;
import com.bgasol.common.core.base.vo.PageVo;
import com.bgasol.model.system.role.entity.RoleEntity;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.List;
@Validated
@ -57,13 +52,4 @@ public abstract class BaseController<
List<ENTITY> all = commonBaseService().findAll();
return BaseVo.success(all);
}
public ResponseEntity<byte[]> downloadImportTemplate() {
return commonBaseService().generateImportTemplateResponse();
}
public BaseVo<ImportResult> importFromExcel( MultipartFile file) throws IOException {
ImportResult importResult = commonBaseService().importFromExcel(file);
return BaseVo.success(importResult, "导入成功"); }
}

View file

@ -0,0 +1,33 @@
package com.bgasol.common.core.base.controller;
import com.bgasol.common.core.base.dto.BaseCreateDto;
import com.bgasol.common.core.base.dto.BasePageDto;
import com.bgasol.common.core.base.dto.BaseUpdateDto;
import com.bgasol.common.core.base.entity.BaseEntity;
import com.bgasol.common.core.base.service.BasePoiService;
import com.bgasol.common.core.base.vo.BaseVo;
import com.bgasol.common.core.base.vo.ImportResult;
import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
public abstract class BasePoiController<
ENTITY extends BaseEntity,
PAGE_DTO extends BasePageDto<ENTITY>,
CREATE_DTO extends BaseCreateDto<ENTITY>,
UPDATE_DTO extends BaseUpdateDto<ENTITY>
> extends BaseController<ENTITY, PAGE_DTO, CREATE_DTO, UPDATE_DTO> {
abstract public BasePoiService<ENTITY, PAGE_DTO, CREATE_DTO, UPDATE_DTO> commonBaseService();
public ResponseEntity<byte[]> downloadImportTemplate() {
return commonBaseService().generateImportTemplateResponse();
}
public BaseVo<ImportResult> importFromExcel(MultipartFile file) throws IOException {
ImportResult importResult = commonBaseService().importFromExcel(file);
return BaseVo.success(importResult, "导入成功");
}
}

View file

@ -0,0 +1,217 @@
package com.bgasol.common.core.base.service;
import cn.idev.excel.EasyExcel;
import com.bgasol.common.core.base.dto.BaseCreateDto;
import com.bgasol.common.core.base.dto.BasePageDto;
import com.bgasol.common.core.base.dto.BaseUpdateDto;
import com.bgasol.common.core.base.entity.BaseEntity;
import com.bgasol.common.core.base.exception.BaseException;
import com.bgasol.common.core.base.listener.BaseExcelImportListener;
import com.bgasol.common.core.base.vo.ImportResult;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@Transactional
@Slf4j
@Service
public abstract class BasePoiService<
ENTITY extends BaseEntity,
PAGE_DTO extends BasePageDto<ENTITY>,
CREATE_DTO extends BaseCreateDto<ENTITY>,
UPDATE_DTO extends BaseUpdateDto<ENTITY>> extends BaseService<ENTITY, PAGE_DTO> {
// DTO类缓存避免重复反射和类加载
private volatile Class<? extends BaseCreateDto<ENTITY>> cachedCreateDtoClass;
// 常量定义
private static final String ENTITY_PACKAGE_SUFFIX = ".entity.";
private static final String DTO_PACKAGE_SUFFIX = ".dto.";
private static final String ENTITY_CLASS_SUFFIX = "Entity";
private static final String CREATE_DTO_CLASS_SUFFIX = "CreateDto";
/**
* 创建模板对应的 DTO 类型自动推断
* 规则 ENTITY 的包路径由 ".entity." 替换为 ".dto."类名后缀由 "Entity" 改为 "CreateDto"
* 例如com.bgasol.model.system.role.entity.RoleEntity -> com.bgasol.model.system.role.dto.RoleCreateDto
* 若推断失败或类不存在则抛出异常
*
* @return 对应的CreateDto类
* @throws BaseException 当无法获取实体类类型或推断DTO类失败时
*/
@SuppressWarnings("unchecked")
protected Class<? extends BaseCreateDto<ENTITY>> commonCreateDtoClass() {
if (cachedCreateDtoClass == null) {
synchronized (this) {
if (cachedCreateDtoClass == null) {
@SuppressWarnings("unchecked")
Class<? extends BaseCreateDto<ENTITY>> resolved = (Class<? extends BaseCreateDto<ENTITY>>) (Class<?>) resolveDtoClass(BaseCreateDto.class, CREATE_DTO_CLASS_SUFFIX);
cachedCreateDtoClass = resolved;
}
}
}
return cachedCreateDtoClass;
}
/**
* 通用DTO解析基于实体类全名 .entity. 替换为 .dto.并用指定后缀替换/追加类名
* 例如RoleEntity -> RoleCreateDto / RoleUpdateDto
*
* @param dtoSuperType 目标DTO的父类型用于类型校验
* @param targetSuffix 目标DTO类名后缀例如 "CreateDto"
* @return 解析并加载后的 DTO Class
*/
@SuppressWarnings("unchecked")
protected <T> Class<? extends T> resolveDtoClass(Class<T> dtoSuperType, String targetSuffix) {
Class<ENTITY> entityClass = commonBaseEntityClass();
if (entityClass == null) {
throw new BaseException("无法获取实体类类型,请检查泛型参数配置");
}
String entityClassName = entityClass.getName();
String dtoClassName = inferDtoClassName(entityClassName, targetSuffix);
try {
ClassLoader classLoader = entityClass.getClassLoader();
Class<?> dtoClass = Class.forName(dtoClassName, false, classLoader);
if (!dtoSuperType.isAssignableFrom(dtoClass)) {
throw new BaseException(String.format("推断的类 %s 不是 %s 的子类", dtoClassName, dtoSuperType.getSimpleName()));
}
return (Class<? extends T>) dtoClass;
} catch (ClassNotFoundException e) {
throw new BaseException(String.format("无法找到DTO类: %s由实体类 %s 推断)", dtoClassName, entityClassName));
} catch (Exception e) {
throw new BaseException(String.format("解析DTO类失败: %s实体类: %s原因: %s", dtoClassName, entityClassName, e.getMessage()));
}
}
private String inferDtoClassName(String entityClassName, String targetSuffix) {
String dtoClassName = entityClassName.replace(ENTITY_PACKAGE_SUFFIX, DTO_PACKAGE_SUFFIX);
if (dtoClassName.endsWith(ENTITY_CLASS_SUFFIX)) {
int suffixIndex = dtoClassName.length() - ENTITY_CLASS_SUFFIX.length();
dtoClassName = dtoClassName.substring(0, suffixIndex) + targetSuffix;
} else {
dtoClassName += targetSuffix;
}
return dtoClassName;
}
/**
* 直接以下载响应的形式返回导入模板
*/
public ResponseEntity<byte[]> generateImportTemplateResponse() {
try {
Schema classSchema = commonBaseEntityClass().getAnnotation(Schema.class);
String templateName = (classSchema != null && StringUtils.isNotBlank(classSchema.description()))
? classSchema.description().replace("实体", "")
: commonBaseEntityClass().getSimpleName().replace("Entity", "");
List<List<String>> head = buildExcelHeadFromEntity();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
EasyExcel.write(outputStream)
.head(head)
.sheet("导入模板")
.doWrite(new ArrayList<>());
byte[] bytes = outputStream.toByteArray();
String fileName = templateName + "_导入模板.xlsx";
String encoded = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replace("+", "%20");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"));
headers.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded);
headers.setContentLength(bytes.length);
return ResponseEntity.ok().headers(headers).body(bytes);
} catch (Exception e) {
log.error("生成导入模板失败", e);
throw new BaseException("生成导入模板失败: " + e.getMessage());
}
}
/**
* 基于实体类 @Schema(description) 注解构建 Excel 单层表头
* 规则
* - 从字段的 @Schema(description) 读取列名
* - 保持字段声明顺序忽略无效或空标题字段不允许重复列名
*/
protected List<List<String>> buildExcelHeadFromEntity() {
Class<?> createDtoClass = commonCreateDtoClass();
List<Field> fields = FieldUtils.getAllFieldsList(createDtoClass);
List<String> titles = fields.stream()
.map(field -> field.getAnnotation(Schema.class))
.filter(Objects::nonNull)
.map(Schema::description)
.filter(StringUtils::isNotBlank)
.toList();
if (titles.isEmpty()) {
throw new BaseException("未能从实体类生成任何列,请检查实体字段与 @Schema(description) 注解配置");
}
return titles.stream()
.map(Collections::singletonList)
.toList();
}
/**
* 通用导入根据表头与字段或 @Schema(description) 名称匹配逐行转换并保存实体
*/
public ImportResult importFromExcel(MultipartFile file) throws IOException {
Class<? extends BaseCreateDto<ENTITY>> dtoClass = commonCreateDtoClass();
BaseExcelImportListener<BaseCreateDto<ENTITY>, ENTITY> listener = BaseExcelImportListener.ofDto(
importBatchSize(),
// 目前还是单次插入
batch -> batch.forEach(this::save),
this::validateImportedEntity,
(e, rowIndex) -> log.warn("导入行异常 row={}, ex={}", rowIndex, e.toString())
);
EasyExcel.read(file.getInputStream(), dtoClass, listener)
.headRowNumber(1)
.sheet()
.doRead();
return ImportResult.builder()
.totalRows(listener.getCurrentRowIndex())
.successRows(listener.getSuccessRows())
.errorRows(listener.getErrors().size())
.errors(listener.getErrors())
.build();
}
/**
* 实体级导入校验返回是否通过默认通过
* 可将错误记录到 errors 列表中如果需要外部收集可在子类维护
*/
protected boolean validateImportedEntity(ENTITY entity, int rowIndex, List<String> errors) {
return true;
}
/**
* 批大小默认 200
*/
protected int importBatchSize() {
return 200;
}
}

View file

@ -1,9 +1,6 @@
package com.bgasol.common.core.base.service;
import cn.idev.excel.EasyExcel;
import com.baomidou.mybatisplus.annotation.TableField;
import com.bgasol.common.core.base.dto.BaseCreateDto;
import com.bgasol.common.core.base.listener.BaseExcelImportListener;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@ -12,17 +9,11 @@ import com.bgasol.common.core.base.entity.BaseEntity;
import com.bgasol.common.core.base.entity.BaseTreeEntity;
import com.bgasol.common.core.base.exception.BaseException;
import com.bgasol.common.core.base.mapper.MyBaseMapper;
import com.bgasol.common.core.base.vo.ImportResult;
import com.bgasol.common.core.base.vo.PageVo;
import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import jakarta.persistence.JoinTable;
import jakarta.persistence.Transient;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.redisson.api.RMapCache;
import org.redisson.api.RedissonClient;
@ -30,15 +21,10 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ResolvableType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;
import java.nio.charset.StandardCharsets;
import static com.bgasol.common.constant.value.RedisConfigValues.DEFAULT_TIME_UNIT;
import static com.bgasol.common.constant.value.RedisConfigValues.randomizeTtl;
@ -67,82 +53,6 @@ public abstract class BaseService<ENTITY extends BaseEntity, PAGE_DTO extends Ba
return (Class<ENTITY>) ResolvableType.forClass(getClass()).as(BaseService.class).getGeneric(0).resolve();
}
// DTO类缓存避免重复反射和类加载
private volatile Class<? extends BaseCreateDto<ENTITY>> cachedCreateDtoClass;
// 常量定义
private static final String ENTITY_PACKAGE_SUFFIX = ".entity.";
private static final String DTO_PACKAGE_SUFFIX = ".dto.";
private static final String ENTITY_CLASS_SUFFIX = "Entity";
private static final String CREATE_DTO_CLASS_SUFFIX = "CreateDto";
/**
* 创建模板对应的 DTO 类型自动推断
* 规则 ENTITY 的包路径由 ".entity." 替换为 ".dto."类名后缀由 "Entity" 改为 "CreateDto"
* 例如com.bgasol.model.system.role.entity.RoleEntity -> com.bgasol.model.system.role.dto.RoleCreateDto
* 若推断失败或类不存在则抛出异常
*
* @return 对应的CreateDto类
* @throws BaseException 当无法获取实体类类型或推断DTO类失败时
*/
@SuppressWarnings("unchecked")
protected Class<? extends BaseCreateDto<ENTITY>> commonCreateDtoClass() {
if (cachedCreateDtoClass == null) {
synchronized (this) {
if (cachedCreateDtoClass == null) {
@SuppressWarnings("unchecked")
Class<? extends BaseCreateDto<ENTITY>> resolved = (Class<? extends BaseCreateDto<ENTITY>>) (Class<?>) resolveDtoClass(BaseCreateDto.class, CREATE_DTO_CLASS_SUFFIX);
cachedCreateDtoClass = resolved;
}
}
}
return cachedCreateDtoClass;
}
/**
* 通用DTO解析基于实体类全名 .entity. 替换为 .dto.并用指定后缀替换/追加类名
* 例如RoleEntity -> RoleCreateDto / RoleUpdateDto
*
* @param dtoSuperType 目标DTO的父类型用于类型校验
* @param targetSuffix 目标DTO类名后缀例如 "CreateDto"
* @return 解析并加载后的 DTO Class
*/
@SuppressWarnings("unchecked")
protected <T> Class<? extends T> resolveDtoClass(Class<T> dtoSuperType, String targetSuffix) {
Class<ENTITY> entityClass = commonBaseEntityClass();
if (entityClass == null) {
throw new BaseException("无法获取实体类类型,请检查泛型参数配置");
}
String entityClassName = entityClass.getName();
String dtoClassName = inferDtoClassName(entityClassName, targetSuffix);
try {
ClassLoader classLoader = entityClass.getClassLoader();
Class<?> dtoClass = Class.forName(dtoClassName, false, classLoader);
if (!dtoSuperType.isAssignableFrom(dtoClass)) {
throw new BaseException(String.format("推断的类 %s 不是 %s 的子类", dtoClassName, dtoSuperType.getSimpleName()));
}
return (Class<? extends T>) dtoClass;
} catch (ClassNotFoundException e) {
throw new BaseException(String.format("无法找到DTO类: %s由实体类 %s 推断)", dtoClassName, entityClassName));
} catch (Exception e) {
throw new BaseException(String.format("解析DTO类失败: %s实体类: %s原因: %s", dtoClassName, entityClassName, e.getMessage()));
}
}
private String inferDtoClassName(String entityClassName, String targetSuffix) {
String dtoClassName = entityClassName.replace(ENTITY_PACKAGE_SUFFIX, DTO_PACKAGE_SUFFIX);
if (dtoClassName.endsWith(ENTITY_CLASS_SUFFIX)) {
int suffixIndex = dtoClassName.length() - ENTITY_CLASS_SUFFIX.length();
dtoClassName = dtoClassName.substring(0, suffixIndex) + targetSuffix;
} else {
dtoClassName += targetSuffix;
}
return dtoClassName;
}
/**
* 保存实体
* 如果实体有中间表也会保存中间表
@ -558,107 +468,4 @@ public abstract class BaseService<ENTITY extends BaseEntity, PAGE_DTO extends Ba
mapCache.remove(id);
}
}
/**
* 直接以下载响应的形式返回导入模板
*/
public ResponseEntity<byte[]> generateImportTemplateResponse() {
try {
Schema classSchema = commonBaseEntityClass().getAnnotation(Schema.class);
String templateName = (classSchema != null && StringUtils.isNotBlank(classSchema.description()))
? classSchema.description().replace("实体", "")
: commonBaseEntityClass().getSimpleName().replace("Entity", "");
List<List<String>> head = buildExcelHeadFromEntity();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
EasyExcel.write(outputStream)
.head(head)
.sheet("导入模板")
.doWrite(new ArrayList<>());
byte[] bytes = outputStream.toByteArray();
String fileName = templateName + "_导入模板.xlsx";
String encoded = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replace("+", "%20");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"));
headers.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encoded);
headers.setContentLength(bytes.length);
return ResponseEntity.ok().headers(headers).body(bytes);
} catch (Exception e) {
log.error("生成导入模板失败", e);
throw new BaseException("生成导入模板失败: " + e.getMessage());
}
}
/**
* 基于实体类 @Schema(description) 注解构建 Excel 单层表头
* 规则
* - 从字段的 @Schema(description) 读取列名
* - 保持字段声明顺序忽略无效或空标题字段不允许重复列名
*/
protected List<List<String>> buildExcelHeadFromEntity() {
Class<?> createDtoClass = commonCreateDtoClass();
List<Field> fields = FieldUtils.getAllFieldsList(createDtoClass);
List<String> titles = fields.stream()
.map(field -> field.getAnnotation(Schema.class))
.filter(Objects::nonNull)
.map(Schema::description)
.filter(StringUtils::isNotBlank)
.toList();
if (titles.isEmpty()) {
throw new BaseException("未能从实体类生成任何列,请检查实体字段与 @Schema(description) 注解配置");
}
return titles.stream()
.map(Collections::singletonList)
.toList();
}
/**
* 通用导入根据表头与字段或 @Schema(description) 名称匹配逐行转换并保存实体
*/
public ImportResult importFromExcel(MultipartFile file) throws IOException {
Class<? extends BaseCreateDto<ENTITY>> dtoClass = commonCreateDtoClass();
BaseExcelImportListener<BaseCreateDto<ENTITY>, ENTITY> listener = BaseExcelImportListener.ofDto(
importBatchSize(),
// 目前还是单次插入
batch -> batch.forEach(this::save),
this::validateImportedEntity,
(e, rowIndex) -> log.warn("导入行异常 row={}, ex={}", rowIndex, e.toString())
);
EasyExcel.read(file.getInputStream(), dtoClass, listener)
.headRowNumber(1)
.sheet()
.doRead();
return ImportResult.builder()
.totalRows(listener.getCurrentRowIndex())
.successRows(listener.getSuccessRows())
.errorRows(listener.getErrors().size())
.errors(listener.getErrors())
.build();
}
/**
* 实体级导入校验返回是否通过默认通过
* 可将错误记录到 errors 列表中如果需要外部收集可在子类维护
*/
protected boolean validateImportedEntity(ENTITY entity, int rowIndex, List<String> errors) {
return true;
}
/**
* 批大小默认 200
*/
protected int importBatchSize() {
return 200;
}
}

View file

@ -2,6 +2,7 @@ package com.bgasol.web.system.role.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.bgasol.common.core.base.controller.BaseController;
import com.bgasol.common.core.base.controller.BasePoiController;
import com.bgasol.common.core.base.dto.BasePageDto;
import com.bgasol.common.core.base.vo.BaseVo;
import com.bgasol.common.core.base.vo.ImportResult;
@ -26,7 +27,7 @@ import java.util.List;
@Tag(name = "角色管理")
@RequestMapping("/role")
@Validated
public class RoleController extends BaseController<
public class RoleController extends BasePoiController<
RoleEntity,
BasePageDto<RoleEntity>,
RoleCreateDto,
@ -80,14 +81,14 @@ public class RoleController extends BaseController<
@GetMapping("/template-download")
@Operation(summary = "下载角色导入模板", operationId = "downloadRoleImportTemplate")
// @SaCheckPermission("role:downloadTemplate")
@SaCheckPermission("role:downloadImportTemplate")
public ResponseEntity<byte[]> downloadImportTemplate() {
return super.downloadImportTemplate();
}
@PostMapping(value = "/import", consumes = {"multipart/form-data"})
@Operation(summary = "导入角色", operationId = "importRole")
// @SaCheckPermission("role:import")
@SaCheckPermission("role:importExcel")
public BaseVo<ImportResult> importExcel(@RequestPart("file") MultipartFile file) throws IOException {
return super.importFromExcel(file);
}

View file

@ -1,15 +1,19 @@
package com.bgasol.web.system.role.service;
import com.bgasol.common.core.base.dto.BasePageDto;
import com.bgasol.common.core.base.service.BaseService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.bgasol.common.core.base.dto.BasePageDto;
import com.bgasol.common.core.base.entity.BaseEntity;
import com.bgasol.common.core.base.service.BasePoiService;
import com.bgasol.model.system.menu.entity.MenuEntity;
import com.bgasol.model.system.permission.entity.PermissionEntity;
import com.bgasol.model.system.role.dto.RoleCreateDto;
import com.bgasol.model.system.role.dto.RoleUpdateDto;
import com.bgasol.model.system.role.entity.RoleEntity;
import com.bgasol.web.system.menu.service.MenuService;
import com.bgasol.web.system.permission.service.PermissionService;
import com.bgasol.web.system.role.mapper.RoleMapper;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -17,13 +21,14 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import com.bgasol.common.core.base.entity.BaseEntity;
@Service
@RequiredArgsConstructor
@Transactional
public class RoleService extends BaseService<RoleEntity, BasePageDto<RoleEntity>> {
public class RoleService extends BasePoiService<RoleEntity,
BasePageDto<RoleEntity>,
RoleCreateDto,
RoleUpdateDto> {
private final RoleMapper roleMapper;
private final MenuService menuService;