mirror of
https://gitee.com/xiaonuobase/snowy.git
synced 2026-03-22 02:37:16 +08:00
【机构】再次优化大数据下的机构授权接口、界面体验
This commit is contained in:
@@ -36,6 +36,7 @@
|
||||
:tree-data="treeData"
|
||||
:field-names="treeFieldNames"
|
||||
:load-data="onLoadData"
|
||||
:height="treeHeight"
|
||||
@select="treeSelect"
|
||||
>
|
||||
</a-tree>
|
||||
@@ -153,7 +154,7 @@
|
||||
import { message } from 'ant-design-vue'
|
||||
import { remove, isEmpty, cloneDeep } from 'lodash-es'
|
||||
import userCenterApi from '@/api/sys/userCenterApi'
|
||||
import { useSlots } from 'vue'
|
||||
import { useSlots, triggerRef } from 'vue'
|
||||
// 弹窗是否打开
|
||||
const visible = ref(false)
|
||||
// 主表格common
|
||||
@@ -252,11 +253,12 @@
|
||||
const current = ref(0) // 当前页数
|
||||
const pageSize = ref(10) // 每页条数
|
||||
const total = ref(0) // 数据总数
|
||||
const treeHeight = ref(400)
|
||||
|
||||
// 懒加载子节点
|
||||
const onLoadData = (treeNode) => {
|
||||
return new Promise((resolve) => {
|
||||
if (typeof props.orgTreeLazyApi !== 'function' || treeNode.dataRef.children) {
|
||||
if (typeof props.orgTreeLazyApi !== 'function' || treeNode.dataRef.children || treeNode.dataRef.isLeaf) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
@@ -271,7 +273,7 @@
|
||||
isLeaf: item.isLeaf === undefined ? false : item.isLeaf
|
||||
}
|
||||
})
|
||||
treeData.value = [...treeData.value]
|
||||
triggerRef(treeData)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
@@ -321,6 +323,8 @@
|
||||
return
|
||||
}
|
||||
visible.value = true
|
||||
// 动态计算树高度,适配不同屏幕
|
||||
treeHeight.value = Math.min(Math.max(window.innerHeight - 350, 250), 460)
|
||||
// 获取机构树
|
||||
if (typeof props.orgTreeLazyApi === 'function') {
|
||||
props
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
:tree-data="treeData"
|
||||
:field-names="treeFieldNames"
|
||||
:load-data="onLoadData"
|
||||
:height="treeHeight"
|
||||
@select="treeSelect"
|
||||
>
|
||||
</a-tree>
|
||||
@@ -153,7 +154,7 @@
|
||||
import { message } from 'ant-design-vue'
|
||||
import { remove, isEmpty, cloneDeep } from 'lodash-es'
|
||||
import userCenterApi from '@/api/sys/userCenterApi'
|
||||
import { useSlots } from 'vue'
|
||||
import { useSlots, triggerRef } from 'vue'
|
||||
// 弹窗是否打开
|
||||
const visible = ref(false)
|
||||
// 主表格common
|
||||
@@ -248,11 +249,12 @@
|
||||
const current = ref(0) // 当前页数
|
||||
const pageSize = ref(10) // 每页条数
|
||||
const total = ref(0) // 数据总数
|
||||
const treeHeight = ref(400)
|
||||
|
||||
// 懒加载子节点
|
||||
const onLoadData = (treeNode) => {
|
||||
return new Promise((resolve) => {
|
||||
if (typeof props.orgTreeLazyApi !== 'function' || treeNode.dataRef.children) {
|
||||
if (typeof props.orgTreeLazyApi !== 'function' || treeNode.dataRef.children || treeNode.dataRef.isLeaf) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
@@ -267,7 +269,7 @@
|
||||
isLeaf: item.isLeaf === undefined ? false : item.isLeaf
|
||||
}
|
||||
})
|
||||
treeData.value = [...treeData.value]
|
||||
triggerRef(treeData)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
@@ -317,6 +319,8 @@
|
||||
return
|
||||
}
|
||||
visible.value = true
|
||||
// 动态计算树高度,适配不同屏幕
|
||||
treeHeight.value = Math.min(Math.max(window.innerHeight - 350, 250), 460)
|
||||
// 获取机构树
|
||||
if (typeof props.orgTreeLazyApi === 'function') {
|
||||
props
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
:tree-data="treeData"
|
||||
:field-names="treeFieldNames"
|
||||
:load-data="onLoadData"
|
||||
:height="treeHeight"
|
||||
@select="treeSelect"
|
||||
>
|
||||
</a-tree>
|
||||
@@ -154,7 +155,7 @@
|
||||
import { message } from 'ant-design-vue'
|
||||
import { remove, isEmpty, cloneDeep } from 'lodash-es'
|
||||
import userCenterApi from '@/api/sys/userCenterApi'
|
||||
import { useSlots } from 'vue'
|
||||
import { useSlots, triggerRef, nextTick } from 'vue'
|
||||
// 弹窗是否打开
|
||||
const visible = ref(false)
|
||||
// 主表格common
|
||||
@@ -255,11 +256,12 @@
|
||||
const current = ref(0) // 当前页数
|
||||
const pageSize = ref(10) // 每页条数
|
||||
const total = ref(0) // 数据总数
|
||||
const treeHeight = ref(400)
|
||||
|
||||
// 懒加载子节点
|
||||
const onLoadData = (treeNode) => {
|
||||
return new Promise((resolve) => {
|
||||
if (typeof props.orgTreeLazyApi !== 'function' || treeNode.dataRef.children) {
|
||||
if (typeof props.orgTreeLazyApi !== 'function' || treeNode.dataRef.children || treeNode.dataRef.isLeaf) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
@@ -274,7 +276,7 @@
|
||||
isLeaf: item.isLeaf === undefined ? false : item.isLeaf
|
||||
}
|
||||
})
|
||||
treeData.value = [...treeData.value]
|
||||
triggerRef(treeData)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
@@ -324,6 +326,8 @@
|
||||
return
|
||||
}
|
||||
visible.value = true
|
||||
// 动态计算树高度,适配不同屏幕
|
||||
treeHeight.value = Math.min(Math.max(window.innerHeight - 350, 250), 460)
|
||||
// 获取机构树
|
||||
if (typeof props.orgTreeLazyApi === 'function') {
|
||||
props
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
:tree-data="treeData"
|
||||
:field-names="treeFieldNames"
|
||||
:load-data="onLoadData"
|
||||
:height="treeHeight"
|
||||
@select="treeSelect"
|
||||
>
|
||||
</a-tree>
|
||||
@@ -163,6 +164,7 @@
|
||||
import { message } from 'ant-design-vue'
|
||||
import { remove, isEmpty, cloneDeep } from 'lodash-es'
|
||||
import userCenterApi from '@/api/sys/userCenterApi'
|
||||
import { triggerRef } from 'vue'
|
||||
// 弹窗是否打开
|
||||
const visible = ref(false)
|
||||
const deleteShow = ref('')
|
||||
@@ -261,6 +263,7 @@
|
||||
const current = ref(0) // 当前页数
|
||||
const pageSize = ref(10) // 每页条数
|
||||
const total = ref(0) // 数据总数
|
||||
const treeHeight = ref(400)
|
||||
// 获取选中列表的api
|
||||
const userListByIdList = (param) => {
|
||||
if (typeof props.userListByIdListApi === 'function') {
|
||||
@@ -287,7 +290,7 @@
|
||||
// 懒加载子节点
|
||||
const onLoadData = (treeNode) => {
|
||||
return new Promise((resolve) => {
|
||||
if (typeof props.orgTreeLazyApi !== 'function' || treeNode.dataRef.children) {
|
||||
if (typeof props.orgTreeLazyApi !== 'function' || treeNode.dataRef.children || treeNode.dataRef.isLeaf) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
@@ -302,7 +305,7 @@
|
||||
isLeaf: item.isLeaf === undefined ? false : item.isLeaf
|
||||
}
|
||||
})
|
||||
treeData.value = [...treeData.value]
|
||||
triggerRef(treeData)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
@@ -315,6 +318,8 @@
|
||||
return
|
||||
}
|
||||
visible.value = true
|
||||
// 动态计算树高度,适配不同屏幕
|
||||
treeHeight.value = Math.min(Math.max(window.innerHeight - 350, 250), 460)
|
||||
// 获取机构树
|
||||
if (typeof props.orgTreeLazyApi === 'function') {
|
||||
props
|
||||
|
||||
@@ -2,8 +2,14 @@
|
||||
<XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0">
|
||||
<template #left>
|
||||
<div ref="treeContainerRef" style="height: 100%">
|
||||
<div
|
||||
v-if="treeLoading && treeData.length === 0"
|
||||
style="display: flex; height: 100%; align-items: center; justify-content: center"
|
||||
>
|
||||
<a-spin />
|
||||
</div>
|
||||
<a-tree
|
||||
v-if="treeData.length > 0"
|
||||
v-else-if="treeData.length > 0"
|
||||
v-model:expandedKeys="defaultExpandedKeys"
|
||||
:tree-data="treeData"
|
||||
:field-names="treeFieldNames"
|
||||
@@ -206,9 +212,13 @@
|
||||
searchFormRef.value.resetFields()
|
||||
tableRef.value.refresh(true)
|
||||
}
|
||||
const treeLoading = ref(true)
|
||||
// 加载左侧的树
|
||||
const loadTreeData = () => {
|
||||
bizOrgApi.orgTreeLazy().then((res) => {
|
||||
treeLoading.value = true
|
||||
bizOrgApi
|
||||
.orgTreeLazy()
|
||||
.then((res) => {
|
||||
if (res !== null) {
|
||||
treeData.value = res.map((item) => {
|
||||
return {
|
||||
@@ -224,6 +234,9 @@
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
treeLoading.value = false
|
||||
})
|
||||
}
|
||||
loadTreeData()
|
||||
// 懒加载子节点
|
||||
|
||||
@@ -2,8 +2,14 @@
|
||||
<XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0">
|
||||
<template #left>
|
||||
<div ref="treeContainerRef" style="height: 100%">
|
||||
<div
|
||||
v-if="treeLoading && treeData.length === 0"
|
||||
style="display: flex; height: 100%; align-items: center; justify-content: center"
|
||||
>
|
||||
<a-spin />
|
||||
</div>
|
||||
<a-tree
|
||||
v-if="treeData.length > 0"
|
||||
v-else-if="treeData.length > 0"
|
||||
v-model:expandedKeys="defaultExpandedKeys"
|
||||
:tree-data="treeData"
|
||||
:field-names="treeFieldNames"
|
||||
@@ -197,9 +203,13 @@
|
||||
searchFormRef.value.resetFields()
|
||||
tableRef.value.refresh(true)
|
||||
}
|
||||
const treeLoading = ref(true)
|
||||
// 加载左侧的树
|
||||
const loadTreeData = () => {
|
||||
bizOrgApi.orgTreeLazy().then((res) => {
|
||||
treeLoading.value = true
|
||||
bizOrgApi
|
||||
.orgTreeLazy()
|
||||
.then((res) => {
|
||||
if (res !== null) {
|
||||
treeData.value = res.map((item) => {
|
||||
return {
|
||||
@@ -208,15 +218,14 @@
|
||||
}
|
||||
})
|
||||
if (isEmpty(defaultExpandedKeys.value)) {
|
||||
// 默认展开顶级
|
||||
treeData.value.forEach((item) => {
|
||||
// 因为0的顶级
|
||||
if (item.parentId === '0') {
|
||||
defaultExpandedKeys.value.push(item.id)
|
||||
if (treeData.value.length > 0) {
|
||||
defaultExpandedKeys.value.push(treeData.value[0].id)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
.finally(() => {
|
||||
treeLoading.value = false
|
||||
})
|
||||
}
|
||||
loadTreeData()
|
||||
|
||||
@@ -2,8 +2,14 @@
|
||||
<XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0">
|
||||
<template #left>
|
||||
<div ref="treeContainerRef" style="height: 100%">
|
||||
<div
|
||||
v-if="treeLoading && treeData.length === 0"
|
||||
style="display: flex; height: 100%; align-items: center; justify-content: center"
|
||||
>
|
||||
<a-spin />
|
||||
</div>
|
||||
<a-tree
|
||||
v-if="treeData.length > 0"
|
||||
v-else-if="treeData.length > 0"
|
||||
v-model:expandedKeys="defaultExpandedKeys"
|
||||
:tree-data="treeData"
|
||||
:field-names="treeFieldNames"
|
||||
@@ -272,9 +278,13 @@
|
||||
searchFormRef.value.resetFields()
|
||||
tableRef.value.refresh(true)
|
||||
}
|
||||
const treeLoading = ref(true)
|
||||
// 加载左侧树
|
||||
const loadTreeData = () => {
|
||||
bizOrgApi.orgTreeLazy().then((res) => {
|
||||
treeLoading.value = true
|
||||
bizOrgApi
|
||||
.orgTreeLazy()
|
||||
.then((res) => {
|
||||
if (res !== null) {
|
||||
treeData.value = res.map((item) => {
|
||||
return {
|
||||
@@ -283,15 +293,14 @@
|
||||
}
|
||||
})
|
||||
if (isEmpty(defaultExpandedKeys.value)) {
|
||||
// 默认展开顶级
|
||||
treeData.value.forEach((item) => {
|
||||
// 因为0的顶级
|
||||
if (item.parentId === '0') {
|
||||
defaultExpandedKeys.value.push(item.id)
|
||||
if (treeData.value.length > 0) {
|
||||
defaultExpandedKeys.value.push(treeData.value[0].id)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
.finally(() => {
|
||||
treeLoading.value = false
|
||||
})
|
||||
}
|
||||
loadTreeData()
|
||||
|
||||
@@ -2,8 +2,14 @@
|
||||
<XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0">
|
||||
<template #left>
|
||||
<div ref="treeContainerRef" style="height: 100%">
|
||||
<div
|
||||
v-if="treeLoading && treeData.length === 0"
|
||||
style="display: flex; height: 100%; align-items: center; justify-content: center"
|
||||
>
|
||||
<a-spin />
|
||||
</div>
|
||||
<a-tree
|
||||
v-if="treeData.length > 0"
|
||||
v-else-if="treeData.length > 0"
|
||||
v-model:expandedKeys="defaultExpandedKeys"
|
||||
:tree-data="treeData"
|
||||
:field-names="treeFieldNames"
|
||||
@@ -203,9 +209,13 @@
|
||||
searchFormRef.value.resetFields()
|
||||
tableRef.value.refresh(true)
|
||||
}
|
||||
const treeLoading = ref(true)
|
||||
// 加载左侧的树
|
||||
const loadTreeData = () => {
|
||||
orgApi.orgTreeLazy().then((res) => {
|
||||
treeLoading.value = true
|
||||
orgApi
|
||||
.orgTreeLazy()
|
||||
.then((res) => {
|
||||
if (res !== null) {
|
||||
treeData.value = res.map((item) => {
|
||||
return {
|
||||
@@ -221,6 +231,9 @@
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
treeLoading.value = false
|
||||
})
|
||||
}
|
||||
loadTreeData()
|
||||
// 懒加载子节点
|
||||
|
||||
@@ -2,8 +2,14 @@
|
||||
<XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0">
|
||||
<template #left>
|
||||
<div ref="treeContainerRef" style="height: 100%">
|
||||
<div
|
||||
v-if="treeLoading && treeData.length === 0"
|
||||
style="display: flex; height: 100%; align-items: center; justify-content: center"
|
||||
>
|
||||
<a-spin />
|
||||
</div>
|
||||
<a-tree
|
||||
v-if="treeData.length > 0"
|
||||
v-else-if="treeData.length > 0"
|
||||
v-model:expandedKeys="defaultExpandedKeys"
|
||||
:tree-data="treeData"
|
||||
:field-names="treeFieldNames"
|
||||
@@ -194,8 +200,11 @@
|
||||
searchFormRef.value.resetFields()
|
||||
tableRef.value.refresh(true)
|
||||
}
|
||||
const treeLoading = ref(true)
|
||||
// 加载左侧的树
|
||||
orgApi.orgTreeLazy().then((res) => {
|
||||
orgApi
|
||||
.orgTreeLazy()
|
||||
.then((res) => {
|
||||
if (res !== null) {
|
||||
treeData.value = res.map((item) => {
|
||||
return {
|
||||
@@ -211,6 +220,9 @@
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
treeLoading.value = false
|
||||
})
|
||||
// 懒加载子节点
|
||||
const onLoadData = (treeNode) => {
|
||||
return new Promise((resolve) => {
|
||||
|
||||
@@ -2,8 +2,14 @@
|
||||
<XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0">
|
||||
<template #left>
|
||||
<div ref="treeContainerRef" style="height: 100%">
|
||||
<div
|
||||
v-if="treeLoading && treeData.length === 0"
|
||||
style="display: flex; height: 100%; align-items: center; justify-content: center"
|
||||
>
|
||||
<a-spin />
|
||||
</div>
|
||||
<a-tree
|
||||
v-if="treeData.length > 0"
|
||||
v-else-if="treeData.length > 0"
|
||||
v-model:expandedKeys="defaultExpandedKeys"
|
||||
:tree-data="treeData"
|
||||
:field-names="treeFieldNames"
|
||||
@@ -244,9 +250,13 @@
|
||||
delete searchFormState.value.category
|
||||
tableRef.value.refresh(true)
|
||||
}
|
||||
const treeLoading = ref(true)
|
||||
// 加载左侧的树
|
||||
const loadTreeData = () => {
|
||||
orgApi.orgTreeLazy().then((res) => {
|
||||
treeLoading.value = true
|
||||
orgApi
|
||||
.orgTreeLazy()
|
||||
.then((res) => {
|
||||
if (res !== null) {
|
||||
// 树中插入全局角色类型
|
||||
const globalRoleType = [
|
||||
@@ -266,15 +276,14 @@
|
||||
})
|
||||
)
|
||||
if (isEmpty(defaultExpandedKeys.value)) {
|
||||
// 默认展开顶级
|
||||
treeData.value.forEach((item) => {
|
||||
// 因为0的顶级
|
||||
if (item.parentId === '0') {
|
||||
defaultExpandedKeys.value.push(item.id)
|
||||
if (treeData.value.length > 0) {
|
||||
defaultExpandedKeys.value.push(treeData.value[0].id)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
.finally(() => {
|
||||
treeLoading.value = false
|
||||
})
|
||||
}
|
||||
loadTreeData()
|
||||
|
||||
@@ -2,8 +2,14 @@
|
||||
<XnResizablePanel direction="row" :initial-size="300" :min-size="200" :max-size="500" :md="0">
|
||||
<template #left>
|
||||
<div ref="treeContainerRef" style="height: 100%">
|
||||
<div
|
||||
v-if="treeLoading && treeData.length === 0"
|
||||
style="display: flex; height: 100%; align-items: center; justify-content: center"
|
||||
>
|
||||
<a-spin />
|
||||
</div>
|
||||
<a-tree
|
||||
v-if="treeData.length > 0"
|
||||
v-else-if="treeData.length > 0"
|
||||
v-model:expandedKeys="defaultExpandedKeys"
|
||||
:tree-data="treeData"
|
||||
:field-names="treeFieldNames"
|
||||
@@ -288,8 +294,11 @@
|
||||
return res
|
||||
})
|
||||
}
|
||||
const treeLoading = ref(true)
|
||||
// 左侧树查询
|
||||
orgApi.orgTreeLazy().then((res) => {
|
||||
orgApi
|
||||
.orgTreeLazy()
|
||||
.then((res) => {
|
||||
if (res !== null) {
|
||||
treeData.value = res.map((item) => {
|
||||
return {
|
||||
@@ -305,6 +314,9 @@
|
||||
}
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
treeLoading.value = false
|
||||
})
|
||||
// 懒加载子节点
|
||||
const onLoadData = (treeNode) => {
|
||||
return new Promise((resolve) => {
|
||||
|
||||
@@ -64,6 +64,15 @@ public class BizOrgServiceImpl extends ServiceImpl<BizOrgMapper, BizOrg> impleme
|
||||
|
||||
private static final String ORG_ALL_LIST_CACHE_KEY = "biz-org:all-list";
|
||||
|
||||
/** 机构数据版本号缓存key,机构增删改时递增,用于使 visibleOrgIds 缓存自动失效 */
|
||||
private static final String ORG_CACHE_VERSION_KEY = "biz-org:cache-version";
|
||||
|
||||
/** 可见机构ID集合缓存key前缀,完整key = 前缀 + 版本号 + : + 数据范围hash */
|
||||
private static final String VISIBLE_ORG_IDS_CACHE_KEY_PREFIX = "biz-org:visible-ids:";
|
||||
|
||||
/** visibleOrgIds 缓存TTL(秒),用于自动清理旧版本的缓存条目 */
|
||||
private static final long VISIBLE_ORG_IDS_CACHE_TTL = 300;
|
||||
|
||||
@Resource
|
||||
private CommonCacheOperator commonCacheOperator;
|
||||
|
||||
@@ -145,47 +154,33 @@ public class BizOrgServiceImpl extends ServiceImpl<BizOrgMapper, BizOrg> impleme
|
||||
return CollectionUtil.newArrayList();
|
||||
}
|
||||
|
||||
// 获取所有机构
|
||||
List<BizOrg> allOrgList = this.getAllOrgList();
|
||||
|
||||
// 构建索引,避免重复线性遍历(O(N) 一次性构建)
|
||||
Map<String, BizOrg> orgById = allOrgList.stream()
|
||||
.collect(Collectors.toMap(BizOrg::getId, org -> org, (a, b) -> a));
|
||||
Map<String, List<BizOrg>> childrenByParentId = allOrgList.stream()
|
||||
.collect(Collectors.groupingBy(BizOrg::getParentId));
|
||||
|
||||
// 计算可见机构ID集合(包含自身及其所有父级),使用索引将复杂度从 O(M*N*D) 降至 O(M*D)
|
||||
Set<String> visibleOrgIds = new HashSet<>();
|
||||
for (String orgId : loginUserDataScope) {
|
||||
if (orgById.containsKey(orgId)) {
|
||||
visibleOrgIds.add(orgId);
|
||||
// 向上遍历添加所有父级
|
||||
String currentId = orgById.get(orgId).getParentId();
|
||||
while (ObjectUtil.isNotEmpty(currentId) && !"0".equals(currentId) && orgById.containsKey(currentId)) {
|
||||
if (!visibleOrgIds.add(currentId)) {
|
||||
break; // 已经添加过,停止遍历
|
||||
}
|
||||
currentId = orgById.get(currentId).getParentId();
|
||||
}
|
||||
}
|
||||
// 从版本化缓存获取可见机构ID集合(命中时无需加载全量数据)
|
||||
Set<String> visibleOrgIds = this.getVisibleOrgIds(loginUserDataScope);
|
||||
if (ObjectUtil.isEmpty(visibleOrgIds)) {
|
||||
return CollectionUtil.newArrayList();
|
||||
}
|
||||
|
||||
// 过滤出当前父级下的可见子级(使用 parentId 索引,O(K) 而非 O(N))
|
||||
List<BizOrg> childList = childrenByParentId.getOrDefault(parentId, Collections.emptyList()).stream()
|
||||
.filter(bizOrg -> visibleOrgIds.contains(bizOrg.getId()))
|
||||
.sorted(Comparator.comparingInt(BizOrg::getSortCode).thenComparing(BizOrg::getId))
|
||||
.collect(Collectors.toList());
|
||||
// SQL直查:获取当前父级下的可见子机构(替代内存过滤2万条记录)
|
||||
List<BizOrg> childList = this.list(new LambdaQueryWrapper<BizOrg>()
|
||||
.eq(BizOrg::getParentId, parentId)
|
||||
.in(BizOrg::getId, visibleOrgIds)
|
||||
.orderByAsc(BizOrg::getSortCode));
|
||||
|
||||
if (ObjectUtil.isEmpty(childList)) {
|
||||
return CollectionUtil.newArrayList();
|
||||
}
|
||||
|
||||
// 判断这些机构是否还有可见子机构(使用 parentId 索引,O(1) 查找子节点列表)
|
||||
// SQL批量查询:判断哪些子机构还有可见的下级(单次SQL替代N次遍历)
|
||||
List<String> childIds = childList.stream().map(BizOrg::getId).collect(Collectors.toList());
|
||||
Set<String> hasChildrenParentIds = this.list(new LambdaQueryWrapper<BizOrg>()
|
||||
.select(BizOrg::getParentId)
|
||||
.in(BizOrg::getParentId, childIds)
|
||||
.in(BizOrg::getId, visibleOrgIds))
|
||||
.stream().map(BizOrg::getParentId).collect(Collectors.toSet());
|
||||
|
||||
return childList.stream().map(bizOrg -> {
|
||||
JSONObject jsonObject = JSONUtil.parseObj(bizOrg);
|
||||
List<BizOrg> subChildren = childrenByParentId.getOrDefault(bizOrg.getId(), Collections.emptyList());
|
||||
boolean hasChildren = subChildren.stream().anyMatch(item -> visibleOrgIds.contains(item.getId()));
|
||||
jsonObject.set("isLeaf", !hasChildren);
|
||||
jsonObject.set("isLeaf", !hasChildrenParentIds.contains(bizOrg.getId()));
|
||||
return jsonObject;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
@@ -231,7 +226,7 @@ public class BizOrgServiceImpl extends ServiceImpl<BizOrgMapper, BizOrg> impleme
|
||||
// 发布增加事件
|
||||
CommonDataChangeEventCenter.doAddWithData(BizDataTypeEnum.ORG.getValue(), JSONUtil.createArray().put(bizOrg));
|
||||
// 清除缓存
|
||||
commonCacheOperator.remove(ORG_ALL_LIST_CACHE_KEY);
|
||||
this.invalidateOrgCaches();
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@@ -268,7 +263,7 @@ public class BizOrgServiceImpl extends ServiceImpl<BizOrgMapper, BizOrg> impleme
|
||||
// 发布更新事件
|
||||
CommonDataChangeEventCenter.doUpdateWithData(BizDataTypeEnum.ORG.getValue(), JSONUtil.createArray().put(bizOrg));
|
||||
// 清除缓存
|
||||
commonCacheOperator.remove(ORG_ALL_LIST_CACHE_KEY);
|
||||
this.invalidateOrgCaches();
|
||||
}
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@@ -323,7 +318,7 @@ public class BizOrgServiceImpl extends ServiceImpl<BizOrgMapper, BizOrg> impleme
|
||||
// 发布删除事件
|
||||
CommonDataChangeEventCenter.doDeleteWithDataIdList(BizDataTypeEnum.ORG.getValue(), toDeleteOrgIdList);
|
||||
// 清除缓存
|
||||
commonCacheOperator.remove(ORG_ALL_LIST_CACHE_KEY);
|
||||
this.invalidateOrgCaches();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,6 +347,65 @@ public class BizOrgServiceImpl extends ServiceImpl<BizOrgMapper, BizOrg> impleme
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使机构相关缓存失效(版本号递增 + 清除全量列表缓存)
|
||||
* 机构增删改时调用,visibleOrgIds 缓存通过版本号自动失效,旧条目由 TTL 自动清理
|
||||
*/
|
||||
private void invalidateOrgCaches() {
|
||||
commonCacheOperator.remove(ORG_ALL_LIST_CACHE_KEY);
|
||||
commonCacheOperator.put(ORG_CACHE_VERSION_KEY, String.valueOf(System.currentTimeMillis()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户可见的机构ID集合(带版本化缓存)
|
||||
* 缓存命中时直接返回,无需加载全量机构数据;缓存未命中时计算并缓存
|
||||
*
|
||||
* @param loginUserDataScope 当前用户的数据范围(直接授权的机构ID列表)
|
||||
* @return 可见机构ID集合(包含授权机构及其所有父级)
|
||||
*/
|
||||
private Set<String> getVisibleOrgIds(List<String> loginUserDataScope) {
|
||||
if (ObjectUtil.isEmpty(loginUserDataScope)) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
// 获取当前缓存版本号
|
||||
Object versionObj = commonCacheOperator.get(ORG_CACHE_VERSION_KEY);
|
||||
String version = versionObj != null ? versionObj.toString() : "0";
|
||||
|
||||
// 构建缓存key:前缀 + 版本号 + : + 数据范围hash
|
||||
int scopeHash = loginUserDataScope.stream().sorted().collect(Collectors.joining(",")).hashCode();
|
||||
String cacheKey = VISIBLE_ORG_IDS_CACHE_KEY_PREFIX + version + ":" + scopeHash;
|
||||
|
||||
// 尝试从缓存获取
|
||||
Object cached = commonCacheOperator.get(cacheKey);
|
||||
if (cached != null) {
|
||||
return new HashSet<>(JSONUtil.toList(JSONUtil.parseArray(cached), String.class));
|
||||
}
|
||||
|
||||
// 缓存未命中,计算可见机构ID集合
|
||||
List<BizOrg> allOrgList = this.getAllOrgList();
|
||||
Map<String, BizOrg> orgById = allOrgList.stream()
|
||||
.collect(Collectors.toMap(BizOrg::getId, org -> org, (a, b) -> a));
|
||||
|
||||
Set<String> visibleOrgIds = new HashSet<>();
|
||||
for (String orgId : loginUserDataScope) {
|
||||
if (orgById.containsKey(orgId)) {
|
||||
visibleOrgIds.add(orgId);
|
||||
// 向上遍历添加所有父级
|
||||
String currentId = orgById.get(orgId).getParentId();
|
||||
while (ObjectUtil.isNotEmpty(currentId) && !"0".equals(currentId) && orgById.containsKey(currentId)) {
|
||||
if (!visibleOrgIds.add(currentId)) {
|
||||
break; // 已经添加过,停止遍历
|
||||
}
|
||||
currentId = orgById.get(currentId).getParentId();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 写入缓存(带TTL,旧版本条目自动过期)
|
||||
commonCacheOperator.put(cacheKey, JSONUtil.toJsonStr(visibleOrgIds), VISIBLE_ORG_IDS_CACHE_TTL);
|
||||
return visibleOrgIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOrgIdByOrgFullNameWithCreate(String orgFullName) {
|
||||
List<BizOrg> allOrgList = this.getAllOrgList();
|
||||
@@ -399,7 +453,7 @@ public class BizOrgServiceImpl extends ServiceImpl<BizOrgMapper, BizOrg> impleme
|
||||
// 发布增加事件
|
||||
CommonDataChangeEventCenter.doAddWithData(BizDataTypeEnum.ORG.getValue(), JSONUtil.createArray().put(bizOrg));
|
||||
// 清除缓存
|
||||
commonCacheOperator.remove(ORG_ALL_LIST_CACHE_KEY);
|
||||
this.invalidateOrgCaches();
|
||||
return bizOrg.getId();
|
||||
}
|
||||
|
||||
@@ -536,6 +590,8 @@ public class BizOrgServiceImpl extends ServiceImpl<BizOrgMapper, BizOrg> impleme
|
||||
}
|
||||
}
|
||||
});
|
||||
// 清除缓存
|
||||
this.invalidateOrgCaches();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user