feat(需求): ①创建/编辑用户菜单权限勾选父菜单自动全选子菜单(el-tree去check-strictly走父子联动,提交前压缩为最小直接授权集,父勾选时后代不重复落库) ②修复管理员勾选店铺数据菜单仍提示'无权查看店铺数据任务'——admin分区授权后若有效授权含店铺数据任务/重复检查菜单(含经'店铺管理'分组展开),自动补内部数据权限 admin_shop_data_crawl_task_data(镜像V84惯例只增不删,回收仍走超管数据范围授权UI)
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref, watch } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
import type { MenuOptionNode } from './user-menu-auth'
|
import type { MenuOptionNode } from './user-menu-auth'
|
||||||
|
import { compactDirectGrantIds } from './user-menu-auth'
|
||||||
import { fetchGrantableMenus } from './user-menu-auth-api'
|
import { fetchGrantableMenus } from './user-menu-auth-api'
|
||||||
|
|
||||||
const props = defineProps<{ checked?: number[] }>()
|
const props = defineProps<{ checked?: number[] }>()
|
||||||
@@ -32,7 +33,10 @@ function onCheck(): void {
|
|||||||
return (Array.isArray(keys) ? keys : []).map((key) => Number(key)).filter((id) => id > 0)
|
return (Array.isArray(keys) ? keys : []).map((key) => Number(key)).filter((id) => id > 0)
|
||||||
}
|
}
|
||||||
const ids = [...collect(adminTree.value), ...collect(appTree.value)]
|
const ids = [...collect(adminTree.value), ...collect(appTree.value)]
|
||||||
emit('update:checked', ids)
|
// 父子联动模式(未启用严格勾选)下勾选父菜单会自动全选全部子菜单;
|
||||||
|
// 提交前压缩为最小直接授权集:父级已勾选时后代不再重复提交(后端按“父级授权
|
||||||
|
// 展开全部后代”判断权限,与旧 admin.html 勾父清后代语义一致)。
|
||||||
|
emit('update:checked', compactDirectGrantIds(ids, data.value))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function load(): Promise<void> {
|
async function load(): Promise<void> {
|
||||||
@@ -62,12 +66,13 @@ watch(
|
|||||||
<div class="menu-group-columns">
|
<div class="menu-group-columns">
|
||||||
<div v-if="adminNodes.length" class="menu-group-block">
|
<div v-if="adminNodes.length" class="menu-group-block">
|
||||||
<div class="menu-group-title">后台</div>
|
<div class="menu-group-title">后台</div>
|
||||||
|
<!-- 父子联动模式(未启用严格勾选):勾选父菜单自动全选全部子菜单,
|
||||||
|
勾父即代表全部后代,与后端“父级授权展开后代”的权限语义一致。 -->
|
||||||
<el-tree
|
<el-tree
|
||||||
ref="adminTree"
|
ref="adminTree"
|
||||||
:data="adminNodes"
|
:data="adminNodes"
|
||||||
node-key="id"
|
node-key="id"
|
||||||
show-checkbox
|
show-checkbox
|
||||||
check-strictly
|
|
||||||
default-expand-all
|
default-expand-all
|
||||||
:props="{ label: 'name', children: 'children' }"
|
:props="{ label: 'name', children: 'children' }"
|
||||||
@check="onCheck"
|
@check="onCheck"
|
||||||
@@ -80,7 +85,6 @@ watch(
|
|||||||
:data="appNodes"
|
:data="appNodes"
|
||||||
node-key="id"
|
node-key="id"
|
||||||
show-checkbox
|
show-checkbox
|
||||||
check-strictly
|
|
||||||
default-expand-all
|
default-expand-all
|
||||||
:props="{ label: 'name', children: 'children' }"
|
:props="{ label: 'name', children: 'children' }"
|
||||||
@check="onCheck"
|
@check="onCheck"
|
||||||
|
|||||||
@@ -105,3 +105,29 @@ export function normalizeMenuIds(raw: unknown): number[] {
|
|||||||
export function buildUserColumnPermissionPayload(raw: unknown): { columnIds: number[] } {
|
export function buildUserColumnPermissionPayload(raw: unknown): { columnIds: number[] } {
|
||||||
return { columnIds: normalizeMenuIds(raw) }
|
return { columnIds: normalizeMenuIds(raw) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把权限树勾选 id 压缩为后端意义上的“直接授权最小集”:父级已勾选时后代无需
|
||||||
|
* 重复提交(后端读取/校验时父级授权会向全部后代展开,与旧 admin.html“勾父清
|
||||||
|
* 后代”语义一致,避免 user_column_permission 存储冗余行)。未勾选父级但单独
|
||||||
|
* 勾选子级的部分授权原样保留。
|
||||||
|
*/
|
||||||
|
export function compactDirectGrantIds(checkedIds: unknown, tree: MenuOptionNode[]): number[] {
|
||||||
|
const ids = normalizeMenuIds(checkedIds)
|
||||||
|
if (ids.length === 0 || tree.length === 0) return ids
|
||||||
|
const idSet = new Set(ids)
|
||||||
|
/** 因“有已勾选祖先”而被父级覆盖、无需直接授权的 id。 */
|
||||||
|
const coveredByAncestor = new Set<number>()
|
||||||
|
const walk = (nodes: MenuOptionNode[], ancestorChecked: boolean): void => {
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (ancestorChecked) {
|
||||||
|
coveredByAncestor.add(node.id)
|
||||||
|
if (node.children?.length) walk(node.children, true)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (node.children?.length) walk(node.children, idSet.has(node.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
walk(tree, false)
|
||||||
|
return ids.filter((id) => !coveredByAncestor.has(id))
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import test from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import { readSource } from './helpers.ts'
|
||||||
|
import { compactDirectGrantIds, type MenuOptionNode } from '../src/pages/account/user-menu-auth.ts'
|
||||||
|
|
||||||
|
// 验收反馈:创建/编辑用户的权限菜单里,勾选父菜单要自动全选子菜单。
|
||||||
|
// 实现:el-tree 去掉 check-strictly 走父子联动;提交时把勾选 id 压缩为最小
|
||||||
|
// 直接授权集(父级已勾选时后代不再重复提交,后端按“父级授权展开后代”判断权限)。
|
||||||
|
|
||||||
|
const tree: MenuOptionNode[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
key: 'root',
|
||||||
|
name: '账号权限',
|
||||||
|
sort: 1,
|
||||||
|
parentId: null,
|
||||||
|
type: 'admin',
|
||||||
|
children: [
|
||||||
|
{ id: 11, key: 'users', name: '用户管理', sort: 1, parentId: 1, type: 'admin' },
|
||||||
|
{ id: 12, key: 'menus', name: '菜单管理', sort: 2, parentId: 1, type: 'admin' },
|
||||||
|
{
|
||||||
|
id: 13,
|
||||||
|
key: 'sub',
|
||||||
|
name: '子分组',
|
||||||
|
sort: 3,
|
||||||
|
parentId: 1,
|
||||||
|
type: 'admin',
|
||||||
|
children: [
|
||||||
|
{ id: 131, key: 'leaf-a', name: '叶子A', sort: 1, parentId: 13, type: 'admin' },
|
||||||
|
{ id: 132, key: 'leaf-b', name: '叶子B', sort: 2, parentId: 13, type: 'admin' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ id: 2, key: 'wb', name: '视频', sort: 1, parentId: null, type: 'app' },
|
||||||
|
]
|
||||||
|
|
||||||
|
test('compactDirectGrantIds_keep_parent_only_when_children_also_checked', () => {
|
||||||
|
// 勾选父 + 联动全选的子:压缩后只保留最上层父级。
|
||||||
|
assert.deepEqual(compactDirectGrantIds([1, 11, 12, 13, 131, 132], tree), [1])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('compactDirectGrantIds_keep_partial_children_when_parent_unchecked', () => {
|
||||||
|
// 父级未勾选、只勾部分子级:原样保留(父级半选,子级为直接授权)。
|
||||||
|
assert.deepEqual(compactDirectGrantIds([11, 131], tree), [11, 131])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('compactDirectGrantIds_keep_topmost_checked_in_multi_level', () => {
|
||||||
|
// 多层嵌套:爷勾选后,父/孙虽也被勾选但均被覆盖,只留爷。
|
||||||
|
assert.deepEqual(compactDirectGrantIds([1, 13, 131], tree), [1])
|
||||||
|
// 爷未勾、子分组勾选时保留子分组(覆盖其叶子)。
|
||||||
|
assert.deepEqual(compactDirectGrantIds([13, 131], tree), [13])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('compactDirectGrantIds_merges_two_groups_and_dedupes', () => {
|
||||||
|
const merged = [...tree, { id: 21, key: 'dup', name: '重复', sort: 1, parentId: null, type: 'admin' }]
|
||||||
|
assert.deepEqual(compactDirectGrantIds([1, 11, 2, 21, 21], merged), [1, 2, 21])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('compactDirectGrantIds_empty_inputs', () => {
|
||||||
|
assert.deepEqual(compactDirectGrantIds([], tree), [])
|
||||||
|
assert.deepEqual(compactDirectGrantIds([11], []), [11])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('user_menu_auth_tree_uses_linked_check_and_compacts', () => {
|
||||||
|
const component = readSource('src/pages/account/UserMenuAuthTree.vue')
|
||||||
|
assert.ok(!/check-strictly/.test(component), '必须去掉 check-strictly,勾父联动全选子')
|
||||||
|
assert.match(component, /compactDirectGrantIds/, '@check 提交需压缩为最小直接授权集')
|
||||||
|
assert.match(component, /default-expand-all/, '默认展开全部菜单')
|
||||||
|
})
|
||||||
+43
@@ -509,6 +509,9 @@ public class PermissionMenuService {
|
|||||||
entity.setColumnId(columnId);
|
entity.setColumnId(columnId);
|
||||||
userColumnPermissionMapper.insert(entity);
|
userColumnPermissionMapper.insert(entity);
|
||||||
}
|
}
|
||||||
|
if (MENU_TYPE_ADMIN.equals(normalizedType)) {
|
||||||
|
autoGrantShopDataDataPermission(userId, finalGrantIds, loadMenus(MENU_TYPE_ADMIN));
|
||||||
|
}
|
||||||
|
|
||||||
// 目标管理员的权限被回收后,其直接创建的员工若仍持有超出范围的
|
// 目标管理员的权限被回收后,其直接创建的员工若仍持有超出范围的
|
||||||
// 授权(创建时快照遗留,员工权限独立于管理员后续变更),必须级联
|
// 授权(创建时快照遗留,员工权限独立于管理员后续变更),必须级联
|
||||||
@@ -516,6 +519,46 @@ public class PermissionMenuService {
|
|||||||
cascadeCleanupSubordinateOverreach(target, finalGrantIds);
|
cascadeCleanupSubordinateOverreach(target, finalGrantIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 镜像 V84 惯例(已获「店铺数据任务管理」菜单的用户自动获得初始数据查看授权):
|
||||||
|
* admin 分区授权更新后,若**有效授权**包含店铺数据相关菜单(任务管理/重复检查,
|
||||||
|
* 含经一级分组「店铺管理」展开覆盖的情形),自动补上内部数据权限
|
||||||
|
* admin_shop_data_crawl_task_data,避免普通管理员已勾了菜单仍提示
|
||||||
|
* “无权查看店铺数据任务”。
|
||||||
|
* 只增不删:数据授权的回收仍走超管专用的「数据范围授权」UI。
|
||||||
|
*/
|
||||||
|
private void autoGrantShopDataDataPermission(Long userId, Set<Long> directGrantIds,
|
||||||
|
List<PermissionMenuEntity> adminMenus) {
|
||||||
|
if (directGrantIds.isEmpty() || adminMenus.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
PermissionMenuEntity taskMenu = findDataPermission("admin_shop_data_crawl_tasks");
|
||||||
|
PermissionMenuEntity duplicateCheckMenu = findDataPermission("admin_shop_data_duplicate_check");
|
||||||
|
PermissionMenuEntity dataPermission = findDataPermission(SHOP_DATA_CRAWL_DATA_PERMISSION_KEY);
|
||||||
|
if (dataPermission == null || dataPermission.getId() == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 直接授权里可能是“店铺管理”这类一级分组:按父子关系展开后再判定。
|
||||||
|
Set<Long> effective = expandDescendantIds(new LinkedHashSet<>(directGrantIds), adminMenus);
|
||||||
|
boolean shopDataRelated = (taskMenu != null && taskMenu.getId() != null
|
||||||
|
&& effective.contains(taskMenu.getId()))
|
||||||
|
|| (duplicateCheckMenu != null && duplicateCheckMenu.getId() != null
|
||||||
|
&& effective.contains(duplicateCheckMenu.getId()));
|
||||||
|
if (!shopDataRelated) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Long existing = userColumnPermissionMapper.countByUserIdAndColumnId(userId, dataPermission.getId());
|
||||||
|
if (existing != null && existing > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UserColumnPermissionEntity grant = new UserColumnPermissionEntity();
|
||||||
|
grant.setUserId(userId);
|
||||||
|
grant.setColumnId(dataPermission.getId());
|
||||||
|
userColumnPermissionMapper.insert(grant);
|
||||||
|
log.info("[shop-data-data-permission] 菜单授权自动补数据查看权限 userId={} dataColumnId={}",
|
||||||
|
userId, dataPermission.getId());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除普通管理员直接创建的员工中,超出该管理员当前有效权限范围的授权。
|
* 删除普通管理员直接创建的员工中,超出该管理员当前有效权限范围的授权。
|
||||||
* 只清理越权项,保留员工合法持有的其他授权。超级管理员无此限制。
|
* 只清理越权项,保留员工合法持有的其他授权。超级管理员无此限制。
|
||||||
|
|||||||
+159
@@ -623,6 +623,165 @@ class PermissionMenuServiceTest {
|
|||||||
assertThat(inserted.getValue().getColumnId()).isEqualTo(76L);
|
assertThat(inserted.getValue().getColumnId()).isEqualTo(76L);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void adminScopedReplacementAutoGrantsShopDataPermissionForTaskMenu() {
|
||||||
|
// 反馈:管理员勾了「店铺数据任务管理」菜单仍提示“无权查看店铺数据任务”。
|
||||||
|
// 镜像 V84 惯例:admin 分区授权含店铺数据任务菜单时自动补内部数据权限。
|
||||||
|
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||||
|
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||||
|
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||||
|
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||||
|
|
||||||
|
PermissionMenuEntity taskMenu = menu(70L, null, "admin", 1);
|
||||||
|
taskMenu.setColumnKey("admin_shop_data_crawl_tasks");
|
||||||
|
PermissionMenuEntity duplicateCheckMenu = menu(71L, null, "admin", 2);
|
||||||
|
duplicateCheckMenu.setColumnKey("admin_shop_data_duplicate_check");
|
||||||
|
PermissionMenuEntity dataPermission = menu(76L, null, "internal", 0);
|
||||||
|
dataPermission.setColumnKey("admin_shop_data_crawl_task_data");
|
||||||
|
when(userMapper.selectById(9L)).thenReturn(user(9L, "super_admin", 1));
|
||||||
|
when(menuMapper.selectList(any())).thenReturn(
|
||||||
|
List.of(taskMenu, duplicateCheckMenu), List.of(taskMenu, duplicateCheckMenu));
|
||||||
|
when(menuMapper.selectOne(any())).thenReturn(taskMenu, duplicateCheckMenu, dataPermission);
|
||||||
|
when(permissionMapper.countByUserIdAndColumnId(any(), any())).thenReturn(0L);
|
||||||
|
|
||||||
|
UserColumnPermissionUpdateRequest request = new UserColumnPermissionUpdateRequest();
|
||||||
|
request.setColumnIds(List.of(70L));
|
||||||
|
service.updateUserColumnPermissions(null, 9L, request, PermissionMenuService.MENU_TYPE_ADMIN);
|
||||||
|
|
||||||
|
ArgumentCaptor<UserColumnPermissionEntity> captor = ArgumentCaptor.forClass(UserColumnPermissionEntity.class);
|
||||||
|
verify(permissionMapper, times(2)).insert(captor.capture());
|
||||||
|
assertThat(captor.getAllValues())
|
||||||
|
.extracting(UserColumnPermissionEntity::getColumnId)
|
||||||
|
.containsExactly(70L, 76L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void adminScopedReplacementAutoGrantsShopDataPermissionViaGroup() {
|
||||||
|
// 反馈场景:直接授权是「店铺管理」一级分组(勾选分组代表整组,前端联动分组全选)
|
||||||
|
// 时,有效展开包含任务菜单,同样自动补内部数据权限。
|
||||||
|
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||||
|
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||||
|
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||||
|
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||||
|
|
||||||
|
PermissionMenuEntity group = menu(60L, null, "admin", 1);
|
||||||
|
group.setColumnKey("admin_group_shop");
|
||||||
|
PermissionMenuEntity taskMenu = menu(70L, 60L, "admin", 2);
|
||||||
|
taskMenu.setColumnKey("admin_shop_data_crawl_tasks");
|
||||||
|
PermissionMenuEntity dataPermission = menu(76L, null, "internal", 0);
|
||||||
|
dataPermission.setColumnKey("admin_shop_data_crawl_task_data");
|
||||||
|
when(userMapper.selectById(9L)).thenReturn(user(9L, "super_admin", 1));
|
||||||
|
when(menuMapper.selectList(any())).thenReturn(List.of(group, taskMenu), List.of(group, taskMenu));
|
||||||
|
when(menuMapper.selectOne(any())).thenReturn(taskMenu, null, dataPermission);
|
||||||
|
when(permissionMapper.countByUserIdAndColumnId(any(), any())).thenReturn(0L);
|
||||||
|
|
||||||
|
UserColumnPermissionUpdateRequest request = new UserColumnPermissionUpdateRequest();
|
||||||
|
request.setColumnIds(List.of(60L));
|
||||||
|
service.updateUserColumnPermissions(null, 9L, request, PermissionMenuService.MENU_TYPE_ADMIN);
|
||||||
|
|
||||||
|
ArgumentCaptor<UserColumnPermissionEntity> captor = ArgumentCaptor.forClass(UserColumnPermissionEntity.class);
|
||||||
|
verify(permissionMapper, times(2)).insert(captor.capture());
|
||||||
|
assertThat(captor.getAllValues())
|
||||||
|
.extracting(UserColumnPermissionEntity::getColumnId)
|
||||||
|
.containsExactly(60L, 76L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void adminScopedReplacementAutoGrantsShopDataPermissionForDuplicateCheckMenu() {
|
||||||
|
// 只勾「店铺数据重复检查」菜单同样自动补数据权限(撞款监控接口要求同一内部数据权限)。
|
||||||
|
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||||
|
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||||
|
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||||
|
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||||
|
|
||||||
|
PermissionMenuEntity taskMenu = menu(70L, null, "admin", 1);
|
||||||
|
taskMenu.setColumnKey("admin_shop_data_crawl_tasks");
|
||||||
|
PermissionMenuEntity duplicateCheckMenu = menu(71L, null, "admin", 2);
|
||||||
|
duplicateCheckMenu.setColumnKey("admin_shop_data_duplicate_check");
|
||||||
|
PermissionMenuEntity dataPermission = menu(76L, null, "internal", 0);
|
||||||
|
dataPermission.setColumnKey("admin_shop_data_crawl_task_data");
|
||||||
|
when(userMapper.selectById(9L)).thenReturn(user(9L, "super_admin", 1));
|
||||||
|
when(menuMapper.selectList(any())).thenReturn(
|
||||||
|
List.of(taskMenu, duplicateCheckMenu), List.of(taskMenu, duplicateCheckMenu));
|
||||||
|
when(menuMapper.selectOne(any())).thenReturn(taskMenu, duplicateCheckMenu, dataPermission);
|
||||||
|
when(permissionMapper.countByUserIdAndColumnId(any(), any())).thenReturn(0L);
|
||||||
|
|
||||||
|
UserColumnPermissionUpdateRequest request = new UserColumnPermissionUpdateRequest();
|
||||||
|
request.setColumnIds(List.of(71L));
|
||||||
|
service.updateUserColumnPermissions(null, 9L, request, PermissionMenuService.MENU_TYPE_ADMIN);
|
||||||
|
|
||||||
|
ArgumentCaptor<UserColumnPermissionEntity> captor = ArgumentCaptor.forClass(UserColumnPermissionEntity.class);
|
||||||
|
verify(permissionMapper, times(2)).insert(captor.capture());
|
||||||
|
assertThat(captor.getAllValues())
|
||||||
|
.extracting(UserColumnPermissionEntity::getColumnId)
|
||||||
|
.containsExactly(71L, 76L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void adminScopedReplacementDoesNotAutoGrantWithoutShopDataMenus() {
|
||||||
|
// 无关菜单(如用户管理)不触发数据权限自动补,避免权限放大。
|
||||||
|
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||||
|
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||||
|
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||||
|
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||||
|
|
||||||
|
PermissionMenuEntity usersMenu = menu(10L, null, "admin", 1);
|
||||||
|
when(userMapper.selectById(9L)).thenReturn(user(9L, "super_admin", 1));
|
||||||
|
when(menuMapper.selectList(any())).thenReturn(List.of(usersMenu), List.of(usersMenu));
|
||||||
|
when(menuMapper.selectOne(any())).thenReturn(null);
|
||||||
|
|
||||||
|
UserColumnPermissionUpdateRequest request = new UserColumnPermissionUpdateRequest();
|
||||||
|
request.setColumnIds(List.of(10L));
|
||||||
|
service.updateUserColumnPermissions(null, 9L, request, PermissionMenuService.MENU_TYPE_ADMIN);
|
||||||
|
|
||||||
|
ArgumentCaptor<UserColumnPermissionEntity> captor = ArgumentCaptor.forClass(UserColumnPermissionEntity.class);
|
||||||
|
verify(permissionMapper).insert(captor.capture());
|
||||||
|
assertThat(captor.getValue().getColumnId()).isEqualTo(10L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void adminScopedReplacementKeepsExistingShopDataPermissionWithoutDuplicate() {
|
||||||
|
// 用户已通过超管「数据范围授权」配过数据权限时不重复插入。
|
||||||
|
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||||
|
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||||
|
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||||
|
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||||
|
|
||||||
|
PermissionMenuEntity taskMenu = menu(70L, null, "admin", 1);
|
||||||
|
taskMenu.setColumnKey("admin_shop_data_crawl_tasks");
|
||||||
|
PermissionMenuEntity dataPermission = menu(76L, null, "internal", 0);
|
||||||
|
dataPermission.setColumnKey("admin_shop_data_crawl_task_data");
|
||||||
|
when(userMapper.selectById(9L)).thenReturn(user(9L, "super_admin", 1));
|
||||||
|
when(menuMapper.selectList(any())).thenReturn(List.of(taskMenu), List.of(taskMenu));
|
||||||
|
when(menuMapper.selectOne(any())).thenReturn(taskMenu, null, dataPermission);
|
||||||
|
when(permissionMapper.countByUserIdAndColumnId(any(), any())).thenReturn(1L);
|
||||||
|
|
||||||
|
UserColumnPermissionUpdateRequest request = new UserColumnPermissionUpdateRequest();
|
||||||
|
request.setColumnIds(List.of(70L));
|
||||||
|
service.updateUserColumnPermissions(null, 9L, request, PermissionMenuService.MENU_TYPE_ADMIN);
|
||||||
|
|
||||||
|
verify(permissionMapper, times(1)).insert(any(UserColumnPermissionEntity.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void appScopedReplacementNeverAutoGrantsShopDataPermission() {
|
||||||
|
// 客户端(app)分区授权不触发数据权限补发(数据权限属后台 admin 体系)。
|
||||||
|
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||||
|
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||||
|
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||||
|
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||||
|
|
||||||
|
when(userMapper.selectById(9L)).thenReturn(user(9L, "super_admin", 1));
|
||||||
|
when(menuMapper.selectList(any())).thenReturn(List.of(menu(11L, null, "app", 1)));
|
||||||
|
when(menuMapper.selectOne(any())).thenReturn(null);
|
||||||
|
|
||||||
|
UserColumnPermissionUpdateRequest request = new UserColumnPermissionUpdateRequest();
|
||||||
|
request.setColumnIds(List.of(11L));
|
||||||
|
service.updateUserColumnPermissions(null, 9L, request, PermissionMenuService.MENU_TYPE_APP);
|
||||||
|
|
||||||
|
verify(permissionMapper, times(1)).insert(any(UserColumnPermissionEntity.class));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void fullCatalogOrdersAppMenusBeforeAdminMenus() {
|
void fullCatalogOrdersAppMenusBeforeAdminMenus() {
|
||||||
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||||
|
|||||||
Reference in New Issue
Block a user