import assert , { AssertionError } from 'assert' ; import { Types } from '@fiora/database/mongoose' ; import stringHash from 'string-hash' ; import config from '@fiora/config/server' ; import getRandomAvatar from '@fiora/utils/getRandomAvatar' ; import Group , { GroupDocument } from '@fiora/database/mongoose/models/group' ; import Socket from '@fiora/database/mongoose/models/socket' ; import Message from '@fiora/database/mongoose/models/message' ; const { isValid } = Types . ObjectId ; param group async function getGroupOnlineMembersHelper ( group ) { const sockets = await Socket . find ( { user : { $in : group . members . map ( ( member ) => member . toString ( ) ) , } , } , { os : 1 , browser : 1 , environment : 1 , user : 1 , } ) . populate ( 'user' , { username : 1 , avatar : 1 } ) ; const filterSockets = sockets . reduce ( ( result , socket ) => { result . set ( socket . user . _id . toString ( ) , socket ) ; return result ; } , new Map ( ) ) ; return Array . from ( filterSockets . values ( ) ) ; } param ctx export async function createGroup ( ctx ) { assert ( ! config . disableCreateGroup , '管理员已关闭创建群组功能' ) ; const ownGroupCount = await Group . count ( { creator : ctx . socket . user } ) ; assert ( ctx . socket . isAdmin || ownGroupCount < config . maxGroupsCount , `创建群组失败, 你已经创建了${ config . maxGroupsCount }个群组` ) ; const { name } = ctx . data ; assert ( name , '群组名不能为空' ) ; const group = await Group . findOne ( { name } ) ; assert ( ! group , '该群组已存在' ) ; let newGroup = null ; try { newGroup = await Group . create ( { name , avatar : getRandomAvatar ( ) , creator : ctx . socket . user , members : [ ctx . socket . user ] , } as GroupDocument ) ; } catch ( err ) { if ( err . name === 'ValidationError' ) { return '群组名包含不支持的字符或者长度超过限制' ; } throw err ; } ctx . socket . join ( newGroup . _id . toString ( ) ) ; return { _id : newGroup . _id , name : newGroup . name , avatar : newGroup . avatar , createTime : newGroup . createTime , creator : newGroup . creator , } ; } param ctx export async function joinGroup ( ctx ) { const { groupId } = ctx . data ; assert ( isValid ( groupId ) , '无效的群组ID' ) ; const group = await Group . findOne ( { _id : groupId } ) ; if ( ! group ) { throw new AssertionError ( { message : '加入群组失败, 群组不存在' } ) ; } assert ( group . members . indexOf ( ctx . socket . user ) === - 1 , '你已经在群组中' ) ; group . members . push ( ctx . socket . user ) ; await group . save ( ) ; const messages = await Message . find ( { toGroup : groupId } , { type : 1 , content : 1 , from : 1 , createTime : 1 , } , { sort : { createTime : - 1 } , limit : 3 } ) . populate ( 'from' , { username : 1 , avatar : 1 } ) ; messages . reverse ( ) ; ctx . socket . join ( group . _id . toString ( ) ) ; return { _id : group . _id , name : group . name , avatar : group . avatar , createTime : group . createTime , creator : group . creator , messages , } ; } param ctx export async function leaveGroup ( ctx ) { const { groupId } = ctx . data ; assert ( isValid ( groupId ) , '无效的群组ID' ) ; const group = await Group . findOne ( { _id : groupId } ) ; if ( ! group ) { throw new AssertionError ( { message : '群组不存在' } ) ; } if ( group . creator ) { assert ( group . creator . toString ( ) !== ctx . socket . user . toString ( ) , '群主不可以退出自己创建的群' ) ; } const index = group . members . indexOf ( ctx . socket . user ) ; assert ( index !== - 1 , '你不在群组中' ) ; group . members . splice ( index , 1 ) ; await group . save ( ) ; ctx . socket . leave ( group . _id . toString ( ) ) ; return { } ; } const GroupOnlineMembersCacheExpireTime = 1000 * 60 ; /**
 * 获取群组在线成员
 */ function getGroupOnlineMembersWrapperV2 ( ) { const cache = { } ; return async function getGroupOnlineMembersV2 ( ctx ) { const { groupId , cache : cacheKey } = ctx . data ; assert ( isValid ( groupId ) , '无效的群组ID' ) ; if ( cache [ groupId ] && cache [ groupId ] . key === cacheKey && cache [ groupId ] . expireTime > Date . now ( ) ) { return { cache : cacheKey } ; } const group = await Group . findOne ( { _id : groupId } ) ; if ( ! group ) { throw new AssertionError ( { message : '群组不存在' } ) ; } const result = await getGroupOnlineMembersHelper ( group ) ; const resultCacheKey = stringHash ( result . map ( ( item ) => item . user . _id ) . join ( ',' ) ) . toString ( 36 ) ; if ( cache [ groupId ] && cache [ groupId ] . key === resultCacheKey ) { cache [ groupId ] . expireTime = Date . now ( ) + GroupOnlineMembersCacheExpireTime ; if ( resultCacheKey === cacheKey ) { return { cache : cacheKey } ; } } cache [ groupId ] = { key : resultCacheKey , value : result , expireTime : Date . now ( ) + GroupOnlineMembersCacheExpireTime , } ; return { cache : resultCacheKey , members : result , } ; } ; } export const getGroupOnlineMembersV2 = getGroupOnlineMembersWrapperV2 ( ) ; export async function getGroupOnlineMembers ( ctx ) { const result = await getGroupOnlineMembersV2 ( ctx ) ; return result . members ; } /**
 * 获取默认群组的在线成员
 * 无需登录态
 */ function getDefaultGroupOnlineMembersWrapper ( ) { let cache = null ; let expireTime = 0 ; return async function getDefaultGroupOnlineMembers ( ) { if ( cache && expireTime > Date . now ( ) ) { return cache ; } const group = await Group . findOne ( { isDefault : true } ) ; if ( ! group ) { throw new AssertionError ( { message : '群组不存在' } ) ; } cache = await getGroupOnlineMembersHelper ( group ) ; expireTime = Date . now ( ) + GroupOnlineMembersCacheExpireTime ; return cache ; } ; } export const getDefaultGroupOnlineMembers = getDefaultGroupOnlineMembersWrapper ( ) ; param ctx export async function changeGroupAvatar ( ctx ) { const { groupId , avatar } = ctx . data ; assert ( isValid ( groupId ) , '无效的群组ID' ) ; assert ( avatar , '头像地址不能为空' ) ; const group = await Group . findOne ( { _id : groupId } ) ; if ( ! group ) { throw new AssertionError ( { message : '群组不存在' } ) ; } assert ( group . creator . toString ( ) === ctx . socket . user . toString ( ) , '只有群主才能修改头像' ) ; await Group . updateOne ( { _id : groupId } , { avatar } ) ; return { } ; } param ctx export async function changeGroupName ( ctx ) { const { groupId , name } = ctx . data ; assert ( isValid ( groupId ) , '无效的群组ID' ) ; assert ( name , '群组名称不能为空' ) ; const group = await Group . findOne ( { _id : groupId } ) ; if ( ! group ) { throw new AssertionError ( { message : '群组不存在' } ) ; } assert ( group . name !== name , '新群组名不能和之前一致' ) ; assert ( group . creator . toString ( ) === ctx . socket . user . toString ( ) , '只有群主才能修改头像' ) ; const targetGroup = await Group . findOne ( { name } ) ; assert ( ! targetGroup , '该群组名已存在' ) ; await Group . updateOne ( { _id : groupId } , { name } ) ; ctx . socket . emit ( groupId , 'changeGroupName' , { groupId , name } ) ; return { } ; } param ctx export async function deleteGroup ( ctx ) { const { groupId } = ctx . data ; assert ( isValid ( groupId ) , '无效的群组ID' ) ; const group = await Group . findOne ( { _id : groupId } ) ; if ( ! group ) { throw new AssertionError ( { message : '群组不存在' } ) ; } assert ( group . creator . toString ( ) === ctx . socket . user . toString ( ) , '只有群主才能解散群组' ) ; assert ( group . isDefault !== true , '默认群组不允许解散' ) ; await Group . deleteOne ( { _id : group } ) ; ctx . socket . emit ( groupId , 'deleteGroup' , { groupId } ) ; return { } ; } export async function getGroupBasicInfo ( ctx ) { const { groupId } = ctx . data ; assert ( isValid ( groupId ) , '无效的群组ID' ) ; const group = await Group . findOne ( { _id : groupId } ) ; if ( ! group ) { throw new AssertionError ( { message : '群组不存在' } ) ; } return { _id : group . _id , name : group . name , avatar : group . avatar , members : group . members . length , } ; }