✨ feat(im): 初始化群申请 v0.5:第六把 review(性能 / 健壮性 / 简洁度收口)
后端
- createInviteRequestList N+1 → 3 SQL:批量 select IN + update IN + insertBatch;20 人邀请从 40 RTT 降到 3 RTT
- service 不再出现 mybatis:复用记录的 update(null, wrapper) 下沉到 Im{Group,Friend}RequestMapper.update*Reset helper
- inviteGroupMember 入参去重切 hutool:CollUtil.subtractToList(CollUtil.distinct(...), activeMemberUserIds)
- 删除 dead 字段 inviterUserId(GroupRequestApprovedNotification / GroupRequestRejectedNotification):前端不再消费
前端
- 1505 / 1506 通知改静默:同意走群事件 1509 / 1510 渲染系统提示,拒绝不再打扰
- 修竞态:addByRequestId 校验 handleResult === UNHANDLED,避免 1503 在途时被 1505 / 1506 抢先后又把已处理记录塞回未处理列表
- 修复 dialog 复用记录刷新:watch key 含 inviterUserId / applyContent,同 id 不同内容也触发 refetch;actingId 期间跳过避免本端动作多余 RTT
- 修复 willGoApproval 误报:group.ownerUserId 兜底群主;members 未到位时保守按非审批处理
- unhandledCountMap memoized getter:O(N) 扫一次缓存到 Map,ConversationItem 直读 Map 消除 O(N×M) 重复 filter
im
parent
cb26df3ca1
commit
808ad575fc
|
|
@ -128,11 +128,7 @@ const visible = computed({
|
|||
set: (value) => emit('update:modelValue', value)
|
||||
})
|
||||
|
||||
/**
|
||||
* 是否走审批:群开启 joinApproval + 当前用户是普通成员;群主 / 管理员邀请绕过审批(对齐后端)
|
||||
*
|
||||
* 用于切换提交后的提示文案:审批分支后端只创建待审批记录,没把人拉进群,提示「等待审批」更准确
|
||||
*/
|
||||
/** 是否走审批:群开启 joinApproval + 当前用户是普通成员;群主 / 管理员邀请直进,不落审批记录 */
|
||||
const willGoApproval = computed(() => {
|
||||
if (!props.groupId) {
|
||||
return false
|
||||
|
|
@ -141,9 +137,20 @@ const willGoApproval = computed(() => {
|
|||
if (!group?.joinApproval) {
|
||||
return false
|
||||
}
|
||||
const myId = Number(userStore.getUser?.id) || 0
|
||||
const myRole = props.members.find((m) => m.userId === myId)?.role
|
||||
return myRole !== ImGroupMemberRole.OWNER && myRole !== ImGroupMemberRole.ADMIN
|
||||
const myId = Number(userStore.getUser?.id)
|
||||
if (!myId) {
|
||||
return false
|
||||
}
|
||||
// 群主直判,避开 members 异步加载的窗口;admin 仍依赖 members
|
||||
if (group.ownerUserId === myId) {
|
||||
return false
|
||||
}
|
||||
// members 未到位时无法判定 admin,保守按非审批处理,宁可漏报「等待审批」也不误报给真实管理员
|
||||
const myRole = props.members.find((member) => member.userId === myId)?.role
|
||||
if (myRole == null) {
|
||||
return false
|
||||
}
|
||||
return myRole !== ImGroupMemberRole.ADMIN
|
||||
})
|
||||
|
||||
const searchText = ref('')
|
||||
|
|
@ -219,7 +226,7 @@ async function handleOk() {
|
|||
submitting.value = true
|
||||
try {
|
||||
await inviteGroupMember({ groupId: props.groupId, memberUserIds })
|
||||
// 审批分支后端仅落待审批记录,没把人拉进群;普通入群直接邀请成功
|
||||
// 审批分支:后端仅落审批记录,未入群
|
||||
message.success(willGoApproval.value ? '邀请已发起,等待群主 / 管理员审批' : '邀请成功')
|
||||
emit('reload', memberUserIds)
|
||||
visible.value = false
|
||||
|
|
|
|||
|
|
@ -204,23 +204,24 @@ watch(
|
|||
)
|
||||
|
||||
/**
|
||||
* 单群模式下订阅 store 中归属本群的未处理列表变化:增 / 减都 refetch 一次拿到最新 handleResult
|
||||
* 单群模式下订阅 store 中归属本群的未处理列表变化:远端事件(WS 1503 新申请 / 其他管理员处理)触发时 refetch
|
||||
* 拿最新 handleResult;本端 agree / refuse 期间 actingId 锁住,跳过本端动作引发的 store 变化避免冗余 RTT
|
||||
*
|
||||
* 触发场景:① WS 1503 收到新申请 → store 头部 unshift;② 其他管理员 / 远端处理 → store 移除该项
|
||||
* 本端 agreeRequest / refuseRequest 内部也会 removeByRequestId 从而触发;fetchList 拉到的 handleResult
|
||||
* 与 updateLocalResult 写的一致,不冲突;仅多一次网络请求,可接受
|
||||
* key 不能只 join id:复用旧记录时同一 requestId 的 applyContent / inviterUserId 会刷新但 id 不变,必须把内容字段也纳入触发
|
||||
*/
|
||||
// TODO @AI:减少缩写,类似 r、curr、prev;还是完整点,不会有啥影响的;
|
||||
watch(
|
||||
() =>
|
||||
props.groupId && visible.value
|
||||
? groupRequestStore.unhandledList
|
||||
.filter((r) => r.groupId === props.groupId)
|
||||
.map((r) => r.id)
|
||||
.filter((request) => request.groupId === props.groupId)
|
||||
.map((request) => `${request.id}:${request.inviterUserId ?? ''}:${request.applyContent ?? ''}`)
|
||||
.join(',')
|
||||
: null,
|
||||
(curr, prev) => {
|
||||
if (curr === null || prev === undefined || curr === prev) {
|
||||
(current, previous) => {
|
||||
if (current === null || previous === undefined || current === previous) {
|
||||
return
|
||||
}
|
||||
if (actingId.value !== null) {
|
||||
return
|
||||
}
|
||||
if (props.groupId) {
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ const requestText = computed(() => {
|
|||
if (!isGroup.value) {
|
||||
return ''
|
||||
}
|
||||
const count = groupRequestStore.getUnhandledCountByGroupId(props.conversation.targetId)
|
||||
const count = groupRequestStore.unhandledCountMap.get(props.conversation.targetId) ?? 0
|
||||
return count > 0 ? `[${count}条进群申请]` : ''
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
refuseGroupRequest as apiRefuseGroupRequest,
|
||||
type ImGroupRequestRespVO
|
||||
} from '@/api/im/group/request'
|
||||
import { ImGroupRequestHandleResult } from '@/views/im/utils/constants'
|
||||
|
||||
/**
|
||||
* IM 加群申请 Store
|
||||
|
|
@ -31,12 +32,21 @@ export const useGroupRequestStore = defineStore('imGroupRequestStore', {
|
|||
}),
|
||||
|
||||
getters: {
|
||||
/** 指定群下的未处理申请数;横幅红点 */
|
||||
getUnhandledCountByGroupId:
|
||||
(state) =>
|
||||
(groupId: number): number =>
|
||||
state.unhandledList.filter((r) => r.groupId === groupId).length,
|
||||
/** 指定群下的未处理申请列表;Drawer 内容 */
|
||||
/**
|
||||
* 各群下未处理申请数的 Map;O(N) 扫一次缓存供 ConversationItem 等 N 处复用,避免 N×M 重复 filter
|
||||
*/
|
||||
unhandledCountMap(state): Map<number, number> {
|
||||
const map = new Map<number, number>()
|
||||
for (const request of state.unhandledList) {
|
||||
map.set(request.groupId, (map.get(request.groupId) ?? 0) + 1)
|
||||
}
|
||||
return map
|
||||
},
|
||||
/** 指定群下的未处理申请数 */
|
||||
getUnhandledCountByGroupId(): (groupId: number) => number {
|
||||
return (groupId: number) => this.unhandledCountMap.get(groupId) ?? 0
|
||||
},
|
||||
/** 指定群下的未处理申请列表 */
|
||||
getUnhandledListByGroupId:
|
||||
(state) =>
|
||||
(groupId: number): ImGroupRequestRespVO[] =>
|
||||
|
|
@ -52,14 +62,14 @@ export const useGroupRequestStore = defineStore('imGroupRequestStore', {
|
|||
},
|
||||
|
||||
/**
|
||||
* WS 收到 1503:按 requestId 单查 + 排到列表头
|
||||
* WS 收到 1503:拉最新内容并置顶
|
||||
*
|
||||
* 同一对 group_id, user_id 的申请记录会被复用,再次申请 / 邀请时 requestId 不变但 applyContent / inviterUserId
|
||||
* 等会刷新;不能因 id 已存在就跳过,必须 fetch 最新内容并把它顶到最前面(与后端 update_time 倒序对齐)
|
||||
* 同一对 group_id, user_id 复用记录时 requestId 不变但 applyContent / inviterUserId 会刷新,所以无条件 fetch + 排到头部
|
||||
* 校验 handleResult:HTTP 在途时若已收到 1505 / 1506,returnedRequest 可能已是已处理状态,不能再塞回未处理列表
|
||||
*/
|
||||
async addByRequestId(requestId: number) {
|
||||
const request = await apiGetMyGroupRequest(requestId)
|
||||
if (!request) {
|
||||
if (!request || request.handleResult !== ImGroupRequestHandleResult.UNHANDLED) {
|
||||
return
|
||||
}
|
||||
this.unhandledList = [request, ...this.unhandledList.filter((r) => r.id !== requestId)]
|
||||
|
|
|
|||
Loading…
Reference in New Issue