You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tailchat/server/services/core/group/group.service.ts

1155 lines
27 KiB
TypeScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import _ from 'lodash';
import { Types } from 'mongoose';
import { isValidStr } from '../../../lib/utils';
import type {
Group,
GroupDocument,
GroupModel,
GroupPanel,
} from '../../../models/group/group';
import {
TcService,
GroupBaseInfo,
TcContext,
TcDbService,
PureContext,
call,
DataNotFoundError,
EntityError,
NoPermissionError,
PERMISSION,
GroupPanelType,
} from 'tailchat-server-sdk';
import moment from 'moment';
interface GroupService
extends TcService,
TcDbService<GroupDocument, GroupModel> {}
class GroupService extends TcService {
get serviceName(): string {
return 'group';
}
onInit(): void {
this.registerLocalDb(require('../../../models/group/group').default);
this.registerAction('createGroup', this.createGroup, {
params: {
name: 'string',
panels: 'array',
},
});
this.registerAction('getUserGroups', this.getUserGroups);
this.registerAction(
'getJoinedGroupAndPanelIds',
this.getJoinedGroupAndPanelIds
);
this.registerAction('getGroupBasicInfo', this.getGroupBasicInfo, {
params: {
groupId: 'string',
},
});
this.registerAction('getGroupInfo', this.getGroupInfo, {
params: {
groupId: 'string',
},
cache: {
keys: ['groupId'],
ttl: 60 * 60, // 1 hour
},
visibility: 'public',
});
this.registerAction('updateGroupField', this.updateGroupField, {
params: {
groupId: 'string',
fieldName: 'string',
fieldValue: 'any',
},
});
this.registerAction('updateGroupConfig', this.updateGroupConfig, {
params: {
groupId: 'string',
configName: 'string',
configValue: 'any',
},
});
this.registerAction('isGroupOwner', this.isGroupOwner, {
params: {
groupId: 'string',
},
});
this.registerAction('joinGroup', this.joinGroup, {
params: {
groupId: 'string',
},
visibility: 'public',
});
this.registerAction('quitGroup', this.quitGroup, {
params: {
groupId: 'string',
},
});
this.registerAction('isMember', this.isMember, {
params: {
groupId: 'string',
},
});
this.registerAction('appendGroupMemberRoles', this.appendGroupMemberRoles, {
params: {
groupId: 'string',
memberIds: { type: 'array', items: 'string' },
roles: { type: 'array', items: 'string' },
},
});
this.registerAction('removeGroupMemberRoles', this.removeGroupMemberRoles, {
params: {
groupId: 'string',
memberIds: { type: 'array', items: 'string' },
roles: { type: 'array', items: 'string' },
},
});
this.registerAction('createGroupPanel', this.createGroupPanel, {
params: {
groupId: 'string',
name: 'string',
type: 'number',
parentId: { type: 'string', optional: true },
provider: { type: 'string', optional: true },
pluginPanelName: { type: 'string', optional: true },
meta: { type: 'object', optional: true },
},
});
this.registerAction('modifyGroupPanel', this.modifyGroupPanel, {
params: {
groupId: 'string',
panelId: 'string',
name: 'string',
type: 'number',
provider: { type: 'string', optional: true },
pluginPanelName: { type: 'string', optional: true },
meta: { type: 'object', optional: true },
},
});
this.registerAction('deleteGroupPanel', this.deleteGroupPanel, {
params: {
groupId: 'string',
panelId: 'string',
},
});
this.registerAction(
'getGroupLobbyConverseId',
this.getGroupLobbyConverseId,
{
params: {
groupId: 'string',
},
}
);
this.registerAction('createGroupRole', this.createGroupRole, {
params: {
groupId: 'string',
roleName: 'string',
permissions: { type: 'array', items: 'string' },
},
});
this.registerAction('deleteGroupRole', this.deleteGroupRole, {
params: {
groupId: 'string',
roleId: 'string',
},
});
this.registerAction('updateGroupRoleName', this.updateGroupRoleName, {
params: {
groupId: 'string',
roleId: 'string',
roleName: 'string',
},
});
this.registerAction(
'updateGroupRolePermission',
this.updateGroupRolePermission,
{
params: {
groupId: 'string',
roleId: 'string',
permissions: {
type: 'array',
items: 'string',
},
},
}
);
this.registerAction('getPermissions', this.getPermissions, {
params: {
groupId: 'string',
},
});
this.registerAction('getUserAllPermissions', this.getUserAllPermissions, {
params: {
groupId: 'string',
userId: 'string',
},
visibility: 'public',
cache: {
keys: ['groupId', 'userId'],
ttl: 60 * 60, // 1 hour
},
});
this.registerAction('muteGroupMember', this.muteGroupMember, {
params: {
groupId: 'string',
memberId: 'string',
muteMs: 'number',
},
});
this.registerAction('deleteGroupMember', this.deleteGroupMember, {
params: {
groupId: 'string',
memberId: 'string',
},
});
}
/**
* 获取群组所有的文字面板id列表
* 用于加入房间
*/
private getGroupTextPanelIds(group: Group): string[] {
// TODO: 先无视权限, 把所有的信息全部显示
const textPanelIds = group.panels
.filter((p) => p.type === GroupPanelType.TEXT)
.map((p) => p.id);
return textPanelIds;
}
/**
* 创建群组
*/
async createGroup(
ctx: TcContext<{
name: string;
panels: GroupPanel[];
}>
) {
const name = ctx.params.name;
const panels = ctx.params.panels;
const userId = ctx.meta.userId;
const group = await this.adapter.model.createGroup({
name,
panels,
owner: userId,
});
const textPanelIds = this.getGroupTextPanelIds(group);
await call(ctx).joinSocketIORoom(
[String(group._id), ...textPanelIds],
userId
);
return this.transformDocuments(ctx, {}, group);
}
async getUserGroups(ctx: TcContext): Promise<Group[]> {
const userId = ctx.meta.userId;
const groups = await this.adapter.model.getUserGroups(userId);
return this.transformDocuments(ctx, {}, groups);
}
/**
* 获取用户所有加入群组的群组id列表与聊天会话id列表
*/
async getJoinedGroupAndPanelIds(ctx: TcContext): Promise<{
groupIds: string[];
panelIds: string[];
}> {
const groups = await this.getUserGroups(ctx); // TODO: 应该使用call而不是直接调用为了获取tracer和caching支持。目前moleculer的文档没有显式的声明类似localCall的行为可以花时间看一下
const panelIds = _.flatten(groups.map((g) => this.getGroupTextPanelIds(g)));
return {
groupIds: groups.map((g) => String(g._id)),
panelIds,
};
}
/**
* 获取群组基本信息
*/
async getGroupBasicInfo(
ctx: PureContext<{
groupId: string;
}>
): Promise<GroupBaseInfo> {
const group = await this.adapter.model
.findById(ctx.params.groupId, {
name: 1,
avatar: 1,
owner: 1,
members: 1,
})
.exec();
if (group === null) {
return null;
}
const groupMemberCount = group.members.length;
return {
name: group.name,
avatar: group.avatar,
owner: String(group.owner),
memberCount: groupMemberCount,
};
}
/**
* 获取群组完整信息
* 仅内部可以访问
*/
async getGroupInfo(ctx: TcContext<{ groupId: string }>): Promise<Group> {
const groupInfo = await this.adapter.model.findById(ctx.params.groupId);
return await this.transformDocuments(ctx, {}, groupInfo);
}
/**
* 修改群组字段
*/
async updateGroupField(
ctx: TcContext<{
groupId: string;
fieldName: string;
fieldValue: unknown;
}>
) {
const { groupId, fieldName, fieldValue } = ctx.params;
const userId = ctx.meta.userId;
const t = ctx.meta.t;
if (
!['name', 'avatar', 'panels', 'roles', 'fallbackPermissions'].includes(
fieldName
)
) {
throw new EntityError(t('该数据不允许修改'));
}
const [isGroupOwner, hasRolePermission] = await call(
ctx
).checkUserPermissions(groupId, userId, [
PERMISSION.core.owner,
PERMISSION.core.manageRoles,
]);
if (fieldName === 'fallbackPermissions') {
if (!hasRolePermission) {
throw new NoPermissionError(t('没有操作权限'));
}
} else if (!isGroupOwner) {
throw new NoPermissionError(t('不是群组管理员无法编辑'));
}
const group = await this.adapter.model.findById(groupId).exec();
group[fieldName] = fieldValue;
await group.save();
if (fieldName === 'fallbackPermissions') {
await this.cleanGroupAllUserPermissionCache(groupId);
}
this.notifyGroupInfoUpdate(ctx, group);
}
/**
* 修改群组配置
*/
async updateGroupConfig(
ctx: TcContext<{
groupId: string;
configName: string;
configValue: unknown;
}>
) {
const { groupId, configName, configValue } = ctx.params;
const userId = ctx.meta.userId;
const t = ctx.meta.t;
const [hasPermission] = await call(ctx).checkUserPermissions(
groupId,
userId,
[PERMISSION.core.groupConfig]
);
if (!hasPermission) {
throw new NoPermissionError(t('没有操作权限'));
}
const group = await this.adapter.model.findById(groupId).exec();
group.config[configName] = configValue;
await group.save();
this.notifyGroupInfoUpdate(ctx, group);
}
/**
* 检测用户是否为群组所有者
*/
async isGroupOwner(
ctx: TcContext<{
groupId: string;
}>
): Promise<boolean> {
const t = ctx.meta.t;
const group = await this.adapter.model.findById(ctx.params.groupId);
if (!group) {
throw new DataNotFoundError(t('没有找到群组'));
}
return String(group.owner) === ctx.meta.userId;
}
/**
* 加入群组
*/
async joinGroup(
ctx: TcContext<{
groupId: string;
}>
) {
const groupId = ctx.params.groupId;
const userId = ctx.meta.userId;
if (!isValidStr(userId)) {
throw new EntityError('用户id为空');
}
if (!isValidStr(groupId)) {
throw new EntityError('群组id为空');
}
const { members } = await this.adapter.model.findById(groupId, {
members: 1,
});
if (members.findIndex((m) => String(m.userId) === userId) >= 0) {
throw new Error('已加入该群组');
}
const doc = await this.adapter.model
.findByIdAndUpdate(
groupId,
{
$addToSet: {
members: {
userId: new Types.ObjectId(userId),
},
},
},
{
new: true,
}
)
.exec();
const group: Group = await this.transformDocuments(ctx, {}, doc);
this.notifyGroupInfoUpdate(ctx, group); // 推送变更
this.unicastNotify(ctx, userId, 'add', group);
const textPanelIds = this.getGroupTextPanelIds(group);
await call(ctx).joinSocketIORoom(
[String(group._id), ...textPanelIds],
userId
);
return group;
}
/**
* 退出群组
*/
async quitGroup(
ctx: TcContext<{
groupId: string;
}>
) {
const groupId = ctx.params.groupId;
const userId = ctx.meta.userId;
const group = await this.adapter.findById(groupId);
if (String(group.owner) === userId) {
// 是群组所有人
await this.adapter.removeById(groupId); // TODO: 后续可以考虑改为软删除
await this.roomcastNotify(ctx, groupId, 'remove', { groupId });
await ctx.call('gateway.leaveRoom', {
roomIds: [groupId],
});
} else {
// 是普通群组成员
const doc = await this.adapter.model
.findByIdAndUpdate(
groupId,
{
$pull: {
members: {
userId: new Types.ObjectId(userId),
},
},
},
{
new: true,
}
)
.exec();
const group: Group = await this.transformDocuments(ctx, {}, doc);
await this.memberLeaveGroup(ctx, group, userId);
}
}
/**
* 检查是否为群组成员
*/
async isMember(ctx: TcContext<{ groupId: string }>) {
const groupId = ctx.params.groupId;
const userId = ctx.meta.userId;
const groupInfo = await call(ctx).getGroupInfo(groupId);
if (!groupInfo) {
// 没有找到群组信息
return false;
}
const members = groupInfo.members;
return members.some((m) => String(m.userId) === userId);
}
/**
* 追加群组成员的角色
*/
async appendGroupMemberRoles(
ctx: TcContext<{
groupId: string;
memberIds: string[];
roles: string[];
}>
) {
const { groupId, memberIds, roles } = ctx.params;
const { userId } = ctx.meta;
await Promise.all(
memberIds.map((memberId) =>
this.adapter.model.updateGroupMemberField(
ctx,
groupId,
memberId,
'roles',
(member) =>
(member['roles'] = _.uniq([...member['roles'], ...roles])),
userId
)
)
);
const group = await this.adapter.model.findById(groupId);
await this.notifyGroupInfoUpdate(ctx, group);
await Promise.all(
memberIds.map((memberId) =>
this.cleanGroupUserPermission(groupId, memberId)
)
);
}
/**
* 移除群组成员的角色
*/
async removeGroupMemberRoles(
ctx: TcContext<{
groupId: string;
memberIds: string[];
roles: string[];
}>
) {
const { groupId, memberIds, roles } = ctx.params;
const { userId } = ctx.meta;
await Promise.all(
memberIds.map((memberId) =>
this.adapter.model.updateGroupMemberField(
ctx,
groupId,
memberId,
'roles',
(member) => (member['roles'] = _.without(member['roles'], ...roles)),
userId
)
)
);
const group = await this.adapter.model.findById(groupId);
await this.notifyGroupInfoUpdate(ctx, group);
await Promise.all(
memberIds.map((memberId) =>
this.cleanGroupUserPermission(groupId, memberId)
)
);
}
/**
* 创建群组面板
*/
async createGroupPanel(
ctx: TcContext<{
groupId: string;
name: string;
type: number;
parentId?: string;
provider?: string;
pluginPanelName?: string;
meta?: object;
}>
) {
const { groupId, name, type, parentId, provider, pluginPanelName, meta } =
ctx.params;
const { t, userId } = ctx.meta;
const [hasPermission] = await call(ctx).checkUserPermissions(
groupId,
userId,
[PERMISSION.core.managePanel]
);
if (!hasPermission) {
throw new NoPermissionError(t('没有操作权限'));
}
const panelId = String(new Types.ObjectId());
const group = await this.adapter.model
.findOneAndUpdate(
{
_id: new Types.ObjectId(groupId),
},
{
$push: {
panels: {
id: panelId,
name,
type,
parentId,
provider,
pluginPanelName,
meta,
},
},
},
{
new: true,
}
)
.exec();
if (type === 0) {
/**
* 如果为文本面板
* 则所有群组成员加入房间
*/
const groupInfo = await call(ctx).getGroupInfo(groupId);
(groupInfo?.members ?? []).map((m) =>
call(ctx).joinSocketIORoom([panelId], m.userId)
);
}
this.notifyGroupInfoUpdate(ctx, group);
}
/**
* 修改群组面板
*/
async modifyGroupPanel(
ctx: TcContext<{
groupId: string;
panelId: string;
name: string;
type: number;
provider?: string;
pluginPanelName?: string;
meta?: object;
}>
) {
const { groupId, panelId, name, type, provider, pluginPanelName, meta } =
ctx.params;
const { t, userId } = ctx.meta;
const [hasPermission] = await call(ctx).checkUserPermissions(
groupId,
userId,
[PERMISSION.core.managePanel]
);
if (!hasPermission) {
throw new NoPermissionError(t('没有操作权限'));
}
const res = await this.adapter.model
.updateOne(
{
_id: new Types.ObjectId(groupId),
},
{
$set: {
'panels.$[element].name': name,
'panels.$[element].type': type,
'panels.$[element].provider': provider,
'panels.$[element].pluginPanelName': pluginPanelName,
'panels.$[element].meta': meta,
},
},
{
new: true,
arrayFilters: [{ 'element.id': panelId }],
}
)
.exec();
if (res.modifiedCount === 0) {
throw new Error(t('没有找到该面板'));
}
const group = await this.adapter.model.findById(String(groupId));
const json = await this.notifyGroupInfoUpdate(ctx, group);
return json;
}
/**
* 删除群组面板
*/
async deleteGroupPanel(ctx: TcContext<{ groupId: string; panelId: string }>) {
const { groupId, panelId } = ctx.params;
const { t, userId } = ctx.meta;
const [hasPermission] = await call(ctx).checkUserPermissions(
groupId,
userId,
[PERMISSION.core.managePanel]
);
if (!hasPermission) {
throw new NoPermissionError(t('没有操作权限'));
}
const group = await this.adapter.model
.findOneAndUpdate(
{
_id: new Types.ObjectId(groupId),
},
{
$pull: {
panels: {
$or: [
{
id: new Types.ObjectId(panelId),
},
{
parentId: new Types.ObjectId(panelId),
},
],
} as any,
},
},
{
new: true,
}
)
.exec();
const json = await this.notifyGroupInfoUpdate(ctx, group);
return json;
}
/**
* 获取群组大厅的会话ID()
*/
async getGroupLobbyConverseId(ctx: TcContext<{ groupId: string }>) {
const groupId = ctx.params.groupId;
const t = ctx.meta.t;
const group = await this.adapter.model.findById(groupId);
if (!group) {
throw new DataNotFoundError(t('群组未找到'));
}
const firstTextPanel = group.panels.find(
(panel) => panel.type === GroupPanelType.TEXT
);
if (!firstTextPanel) {
return null;
}
return firstTextPanel.id;
}
/**
* 创建群组角色
*/
async createGroupRole(
ctx: TcContext<{ groupId: string; roleName: string; permissions: string[] }>
) {
const { groupId, roleName, permissions } = ctx.params;
const { userId, t } = ctx.meta;
const [hasPermission] = await call(ctx).checkUserPermissions(
groupId,
userId,
[PERMISSION.core.managePanel]
);
if (!hasPermission) {
throw new NoPermissionError(t('没有操作权限'));
}
const group = await this.adapter.model
.findOneAndUpdate(
{
_id: new Types.ObjectId(groupId),
},
{
$push: {
roles: {
name: roleName,
permissions,
},
},
},
{
new: true,
}
)
.exec();
const json = await this.notifyGroupInfoUpdate(ctx, group);
return json;
}
/**
* 删除群组角色
*/
async deleteGroupRole(ctx: TcContext<{ groupId: string; roleId: string }>) {
const { groupId, roleId } = ctx.params;
const { userId, t } = ctx.meta;
const [hasPermission] = await call(ctx).checkUserPermissions(
groupId,
userId,
[PERMISSION.core.manageRoles]
);
if (!hasPermission) {
throw new NoPermissionError(t('没有操作权限'));
}
const group = await this.adapter.model
.findOneAndUpdate(
{
_id: new Types.ObjectId(groupId),
},
{
$pull: {
roles: {
_id: roleId,
}, // 删除角色
'members.$[].roles': roleId, // 删除成员角色
},
},
{
new: true,
}
)
.exec();
const json = await this.notifyGroupInfoUpdate(ctx, group);
return json;
}
/**
* 更新群组角色权限
*/
async updateGroupRoleName(
ctx: TcContext<{
groupId: string;
roleId: string;
roleName: string;
}>
) {
const { groupId, roleId, roleName } = ctx.params;
const { userId, t } = ctx.meta;
const [hasPermission] = await call(ctx).checkUserPermissions(
groupId,
userId,
[PERMISSION.core.managePanel]
);
if (!hasPermission) {
throw new NoPermissionError(t('没有操作权限'));
}
const group = await this.adapter.model.updateGroupRoleName(
groupId,
roleId,
roleName
);
const json = await this.notifyGroupInfoUpdate(ctx, group);
return json;
}
/**
* 更新群组角色权限
*/
async updateGroupRolePermission(
ctx: TcContext<{
groupId: string;
roleId: string;
permissions: string[];
}>
) {
const { groupId, roleId, permissions } = ctx.params;
const { userId, t } = ctx.meta;
const [hasPermission] = await call(ctx).checkUserPermissions(
groupId,
userId,
[PERMISSION.core.managePanel]
);
if (!hasPermission) {
throw new NoPermissionError(t('没有操作权限'));
}
const group = await this.adapter.model.updateGroupRolePermission(
groupId,
roleId,
permissions
);
const json = await this.notifyGroupInfoUpdate(ctx, group);
return json;
}
/**
* 获取群组成员权限(对外)
*/
async getPermissions(
ctx: TcContext<{
groupId: string;
}>
): Promise<string[]> {
const { groupId } = ctx.params;
const userId = ctx.meta.userId;
const permissions = await this.localCall('getUserAllPermissions', {
groupId,
userId,
});
return permissions;
}
/**
* 获取群组成员权限(对内)
* 带有groupId和userId的缓存
*/
async getUserAllPermissions(
ctx: TcContext<{
groupId: string;
userId: string;
}>
): Promise<string[]> {
const { groupId, userId } = ctx.params;
const permissions = await this.adapter.model.getGroupUserPermission(
groupId,
userId
);
return permissions;
}
/**
* 禁言群组成员
*/
async muteGroupMember(
ctx: TcContext<{
groupId: string;
memberId: string;
muteMs: number; // 禁言多少多少毫秒. 精确到ms, 如果小于0则视为解除禁言
}>
) {
const { groupId, memberId, muteMs } = ctx.params;
const userId = ctx.meta.userId;
const language = ctx.meta.language;
const isUnmute = muteMs < 0;
const group = await this.adapter.model.updateGroupMemberField(
ctx,
groupId,
memberId,
'muteUntil',
isUnmute ? undefined : new Date(new Date().valueOf() + muteMs),
userId
);
this.notifyGroupInfoUpdate(ctx, group);
const memberInfo = await call(ctx).getUserInfo(memberId);
if (isUnmute) {
await call(ctx).addGroupSystemMessage(
groupId,
`${ctx.meta.user.nickname} 解除了 ${memberInfo.nickname} 的禁言`
);
} else {
await call(ctx).addGroupSystemMessage(
groupId,
`${ctx.meta.user.nickname} 禁言了 ${memberInfo.nickname} ${moment
.duration(muteMs, 'ms')
.locale(language)
.humanize()}`
);
}
}
async deleteGroupMember(
ctx: TcContext<{
groupId: string;
memberId: string;
}>
) {
const { groupId, memberId } = ctx.params;
const userId = ctx.meta.userId;
const t = ctx.meta.t;
// 检查是否在踢自己
if (String(memberId) === String(userId)) {
throw new Error(t('不允许踢出自己'));
}
// 检查是否有权限
const [hasPermission] = await call(ctx).checkUserPermissions(
groupId,
userId,
[PERMISSION.core.manageUser]
);
if (!hasPermission) {
throw new NoPermissionError(t('没有操作权限'));
}
// 检查是否踢出了不该踢出的人
const groupInfo = await call(ctx).getGroupInfo(groupId);
if (String(memberId) === String(groupInfo.owner)) {
throw new Error(t('不允许踢出群组OP'));
}
const group = await this.adapter.model.findByIdAndUpdate(
groupId,
{
$pull: {
members: {
userId: String(memberId),
},
},
},
{ new: true }
);
await this.memberLeaveGroup(ctx, group, memberId);
const memberInfo = await call(ctx).getUserInfo(memberId);
await call(ctx).addGroupSystemMessage(
groupId,
`${ctx.meta.user.nickname}${memberInfo.nickname} 移出了本群组`
);
}
/**
* 退出群组流程
* 用于踢出群组成员和主动退出群组
*
* 先将自己退出房间, 然后再进行房间级别通知
*/
private async memberLeaveGroup(
ctx: TcContext,
group: Group,
memberId: string
) {
const groupId = String(group._id);
await ctx.call('gateway.leaveRoom', {
roomIds: [
groupId,
...group.panels
.filter((p) => p.type === GroupPanelType.TEXT)
.map((p) => p.id),
], // 离开群组和所有面板房间
memberId,
});
await Promise.all([
this.unicastNotify(ctx, memberId, 'remove', { groupId }),
this.notifyGroupInfoUpdate(ctx, group),
]);
}
/**
* 发送通知群组信息发生变更
*
* 发送通知时会同时清空群组信息缓存
*/
private async notifyGroupInfoUpdate(
ctx: TcContext,
group: Group
): Promise<Group> {
const groupId = String(group._id);
let json = group;
if (_.isPlainObject(group) === false) {
// 当传入的数据为group doc时
json = await this.transformDocuments(ctx, {}, group);
}
this.cleanGroupInfoCache(groupId);
await this.roomcastNotify(ctx, groupId, 'updateInfo', json);
return json;
}
/**
* 清理群组缓存
* @param groupId 群组id
*/
private cleanGroupInfoCache(groupId: string) {
this.cleanActionCache('getGroupInfo', [groupId]);
}
/**
* 清理群组用户缓存
* @param groupId 群组id
* @param userId 用户id
*/
private cleanGroupUserPermission(groupId: string, userId: string) {
this.cleanActionCache('getUserAllPermissions', [groupId, userId]);
}
/**
* 清理群组所有用户缓存
* @param groupId 群组id
*/
private cleanGroupAllUserPermissionCache(groupId: string) {
this.cleanActionCache('getUserAllPermissions', [groupId]);
}
}
export default GroupService;