docs(product): 删除产品管理SQL口径和业务设计文档

- 移除02-产品管理SQL已确认口径文档
- 移除02-产品管理业务设计文档
- 清理产品管理模块的详细设计说明
- 删除产品需求状态字段口径定义
- 移除来源承接与需求拆分口径说明
- 清理需求终态原因承接口径内容
- 删除产品生命周期管理设计
- 移除产品团队权限管理规范
- 清理产品与项目关系约束说明
- 删除轻量需求管理业务规则
- 移除产品状态机与流程设计
- 清理权限与动作矩阵定义
This commit is contained in:
2026-04-22 18:18:38 +08:00
parent f8231c2d51
commit 2943a6255b
74 changed files with 3527 additions and 2835 deletions

View File

@@ -12,6 +12,7 @@ public interface DictTypeConstants {
String USER_SEX = "system_user_sex";
String LOGIN_TYPE = "system_login_type";
String LOGIN_RESULT = "system_login_result";
String OBJECT_DIRECTION = "rdms_object_direction";
String JOB_STATUS = "infra_job_status";
String JOB_LOG_STATUS = "infra_job_log_status";

View File

@@ -26,7 +26,10 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static com.njcn.rdms.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
@@ -79,13 +82,26 @@ public class DictDataController {
@GetMapping(value = {"/list-all-simple", "simple-list"})
@Operation(summary = "获得全部字典数据列表", description = "一般用于管理后台缓存字典数据在本地")
// 无需添加权限认证,因为前端全局都需要
// 不额外校验菜单权限,前端在登录后可直接拉取并缓存
public CommonResult<List<DictDataSimpleRespVO>> getSimpleDictDataList() {
List<DictDataDO> list = dictDataService.getDictDataList(
CommonStatusEnum.ENABLE.getStatus(), null);
return success(BeanUtils.toBean(list, DictDataSimpleRespVO.class));
}
@GetMapping("/frontend-cache")
@Operation(summary = "获得前端运行时字典缓存")
public CommonResult<Map<String, List<DictDataSimpleRespVO>>> getFrontendCache() {
List<DictDataSimpleRespVO> list = BeanUtils.toBean(
dictDataService.getDictDataList(CommonStatusEnum.ENABLE.getStatus(), null),
DictDataSimpleRespVO.class);
Map<String, List<DictDataSimpleRespVO>> result = new LinkedHashMap<>();
// 基于 service 已排好序的结果分组,保证每个字典组内仍按 sort 升序返回。
list.forEach(dictData -> result.computeIfAbsent(dictData.getDictType(),
key -> new ArrayList<>()).add(dictData));
return success(result);
}
@GetMapping("/page")
@Operation(summary = "获得字典类型的分页")
@PreAuthorize("@ss.hasPermission('system:dict:query')")

View File

@@ -16,6 +16,9 @@ public class DictDataSimpleRespVO {
@Schema(description = "字典标签", requiredMode = Schema.RequiredMode.REQUIRED, example = "")
private String label;
@Schema(description = "排序值", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer sort;
@Schema(description = "颜色类型default、primary、success、info、warning、danger", example = "default")
private String colorType;

View File

@@ -0,0 +1,45 @@
package com.njcn.rdms.module.system.framework.cache;
import com.njcn.rdms.module.system.dal.redis.RedisKeyConstants;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 系统启动后清理权限相关缓存,避免 SQL 直改后继续命中旧权限数据。
*/
@Component
@Slf4j
@RequiredArgsConstructor
public class PermissionCacheStartupCleaner implements ApplicationRunner {
private static final List<String> CACHE_NAMES = List.of(
RedisKeyConstants.ROLE,
RedisKeyConstants.USER_ROLE_ID_LIST,
RedisKeyConstants.MENU_ROLE_ID_LIST,
RedisKeyConstants.PERMISSION_MENU_ID_LIST
);
private final CacheManager cacheManager;
@Override
public void run(ApplicationArguments args) {
CACHE_NAMES.forEach(this::clearCacheQuietly);
}
private void clearCacheQuietly(String cacheName) {
Cache cache = cacheManager.getCache(cacheName);
if (cache == null) {
log.debug("[clearCacheQuietly][cacheName({}) 未注册,跳过清理]", cacheName);
return;
}
cache.clear();
}
}

View File

@@ -3,8 +3,6 @@ package com.njcn.rdms.module.system.service.permission;
import java.util.Collection;
import java.util.Set;
import static java.util.Collections.singleton;
/**
* 权限 Service 接口
* <p>
@@ -61,9 +59,7 @@ public interface PermissionService {
* @param roleId 角色编号
* @return 菜单编号集合
*/
default Set<Long> getRoleMenuListByRoleId(Long roleId) {
return getRoleMenuListByRoleId(singleton(roleId));
}
Set<Long> getRoleMenuListByRoleId(Long roleId);
/**
* 获得角色们拥有的菜单编号集合

View File

@@ -28,7 +28,10 @@ import org.springframework.transaction.annotation.Transactional;
import jakarta.annotation.Resource;
import java.util.*;
import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.njcn.rdms.framework.common.util.collection.CollectionUtils.convertSet;
import static com.njcn.rdms.module.system.enums.ErrorCodeConstants.ROLE_IS_DISABLE;
import static com.njcn.rdms.module.system.enums.ErrorCodeConstants.ROLE_NOT_EXISTS;
/**
* 权限 Service 实现类
@@ -179,8 +182,7 @@ public class PermissionServiceImpl implements PermissionService {
allEntries = true) // allEntries 清空所有缓存,主要一次更新涉及到的 menuIds 较多,反倒批量会更快
})
public void assignRoleMenu(Long roleId, Set<Long> menuIds) {
roleService.validateRoleList(Collections.singleton(roleId), GLOBAL_SCOPE_TYPE, GLOBAL_OBJECT_TYPE);
RoleDO role = roleService.getRole(roleId);
RoleDO role = getEnabledRole(roleId);
menuService.validateMenuList(menuIds, role.getScopeType(), role.getObjectType());
// 获得角色拥有菜单编号
Set<Long> dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId);
@@ -224,6 +226,12 @@ public class PermissionServiceImpl implements PermissionService {
roleMenuMapper.deleteListByMenuId(menuId);
}
@Override
public Set<Long> getRoleMenuListByRoleId(Long roleId) {
RoleDO role = getEnabledRole(roleId);
return getRoleMenuListByRoleId(Collections.singleton(roleId), role.getScopeType(), role.getObjectType());
}
@Override
public Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds) {
if (CollUtil.isEmpty(roleIds)) {
@@ -338,6 +346,17 @@ public class PermissionServiceImpl implements PermissionService {
return CollUtil.isEmpty(menus) ? null : menus.get(0);
}
private RoleDO getEnabledRole(Long roleId) {
RoleDO role = roleService.getRole(roleId);
if (role == null) {
throw exception(ROLE_NOT_EXISTS);
}
if (!CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())) {
throw exception(ROLE_IS_DISABLE, role.getName());
}
return role;
}
private PermissionServiceImpl getSelf() {
return SpringUtil.getBean(getClass());
}

View File

@@ -0,0 +1,57 @@
package com.njcn.rdms.module.system.framework.cache;
import com.njcn.rdms.framework.test.core.ut.BaseMockitoUnitTest;
import com.njcn.rdms.module.system.dal.redis.RedisKeyConstants;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.boot.ApplicationArguments;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
class PermissionCacheStartupCleanerTest extends BaseMockitoUnitTest {
@Mock
private CacheManager cacheManager;
@Mock
private Cache roleCache;
@Mock
private Cache userRoleCache;
@Mock
private Cache menuRoleCache;
@Mock
private Cache permissionMenuCache;
@InjectMocks
private PermissionCacheStartupCleaner cleaner;
@Test
void run_shouldClearPermissionRelatedCaches() throws Exception {
when(cacheManager.getCache(RedisKeyConstants.ROLE)).thenReturn(roleCache);
when(cacheManager.getCache(RedisKeyConstants.USER_ROLE_ID_LIST)).thenReturn(userRoleCache);
when(cacheManager.getCache(RedisKeyConstants.MENU_ROLE_ID_LIST)).thenReturn(menuRoleCache);
when(cacheManager.getCache(RedisKeyConstants.PERMISSION_MENU_ID_LIST)).thenReturn(permissionMenuCache);
cleaner.run(mock(ApplicationArguments.class));
verify(roleCache).clear();
verify(userRoleCache).clear();
verify(menuRoleCache).clear();
verify(permissionMenuCache).clear();
}
@Test
void run_shouldIgnoreMissingCaches() {
when(cacheManager.getCache(RedisKeyConstants.ROLE)).thenReturn(roleCache);
assertDoesNotThrow(() -> cleaner.run(mock(ApplicationArguments.class)));
verify(roleCache).clear();
}
}