Browse Source

专家抽取算法优化

master
WendyYang 1 year ago
parent
commit
7486f01a1a
5 changed files with 183 additions and 155 deletions
  1. +6
    -3
      pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/dto/InviteCacheDTO.java
  2. +114
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/meeting/helper/ExpertRandomInviteAlgorithm.java
  3. +12
    -101
      pmapi/src/main/java/com/ningdatech/pmapi/meeting/manage/ExpertInviteManage.java
  4. +46
    -51
      pmapi/src/main/java/com/ningdatech/pmapi/meeting/task/ExpertInviteTask.java
  5. +5
    -0
      pmapi/src/main/java/com/ningdatech/pmapi/meeting/task/RandomInviteProperties.java

+ 6
- 3
pmapi/src/main/java/com/ningdatech/pmapi/meeting/entity/dto/InviteCacheDTO.java View File

@@ -17,17 +17,20 @@ public final class InviteCacheDTO {

private Long meetingId;

private Boolean invitedRefused;
/**
* 拒绝的专家是否可以再次邀请
*/
private Boolean reInvite;

/**
* 任务触发时间
*/
private LocalDateTime taskStartTime;

public static InviteCacheDTO of(Long meetingId, Boolean invitedRefused, LocalDateTime startTime) {
public static InviteCacheDTO of(Long meetingId, Boolean reInvite, LocalDateTime startTime) {
InviteCacheDTO bo = new InviteCacheDTO();
bo.setMeetingId(meetingId);
bo.setInvitedRefused(invitedRefused);
bo.setReInvite(reInvite);
bo.setTaskStartTime(startTime);
return bo;
}


+ 114
- 0
pmapi/src/main/java/com/ningdatech/pmapi/meeting/helper/ExpertRandomInviteAlgorithm.java View File

@@ -0,0 +1,114 @@
package com.ningdatech.pmapi.meeting.helper;

import cn.hutool.core.util.RandomUtil;
import com.ningdatech.pmapi.expert.entity.ExpertUserFullInfo;
import com.ningdatech.pmapi.meeting.entity.domain.MeetingExpert;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.RandomUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
* <p>
* 专家抽取算法
* </p>
*
* @author WendyYang
* @since 2023/4/14
**/
public class ExpertRandomInviteAlgorithm {

/**
* 每个单位只抽取一人
*
* @param expertGroupByUnit 需要抽取的人
* @param expertsByRecentMeeting 最近会议抽取到的专家
* @param count 抽取数量
* @return 抽取到的专家信息
* @author WendyYang
**/
public static List<ExpertUserFullInfo> inviteGroupByCompany(Map<String, List<ExpertUserFullInfo>> expertGroupByUnit,
List<List<MeetingExpert>> expertsByRecentMeeting,
Integer count) {
if (MapUtils.isEmpty(expertGroupByUnit)) {
return Collections.emptyList();
}
if (expertsByRecentMeeting.isEmpty()) {
return expertGroupByUnit.values().stream()
.map(RandomUtil::randomEle)
.limit(count).collect(Collectors.toList());
} else {
List<ExpertUserFullInfo> result = new ArrayList<>();
List<String> keySet = new ArrayList<>(expertGroupByUnit.keySet());
for (int i = 0; i < count; i++) {
String company = keySet.get(RandomUtils.nextInt(0, keySet.size()));
List<ExpertUserFullInfo> expertsByCompany = expertGroupByUnit.get(company);
for (List<MeetingExpert> experts : expertsByRecentMeeting) {
List<ExpertUserFullInfo> notInvitedUsers = expertsByCompany.stream()
.filter(w -> experts.stream().noneMatch(expert -> expert.getExpertId().equals(w.getUserId())))
.collect(Collectors.toList());
if (!notInvitedUsers.isEmpty()) {
result.add(RandomUtil.randomEle(notInvitedUsers));
break;
} else if (expertsByRecentMeeting.indexOf(experts) == (expertsByRecentMeeting.size() - 1)) {
result.add(RandomUtil.randomEle(expertsByCompany));
}
}
if (result.size() < count) {
keySet.remove(company);
if (keySet.isEmpty()) {
break;
}
}
}
return result;
}
}

/**
* 随机抽取专家
*
* @param userFullInfos 可抽取的所有专家
* @param expertsByRecentMeeting 最近会议抽取到的专家
* @param count 抽取数量
* @return 抽取到的专家信息
* @author WendyYang
**/
public static List<ExpertUserFullInfo> inviteWithoutCompany(List<ExpertUserFullInfo> userFullInfos,
List<List<MeetingExpert>> expertsByRecentMeeting,
Integer count) {
List<ExpertUserFullInfo> result;
if (expertsByRecentMeeting.isEmpty()) {
result = RandomUtil.randomEleList(userFullInfos, count);
} else {
result = new ArrayList<>();
for (List<MeetingExpert> experts : expertsByRecentMeeting) {
List<ExpertUserFullInfo> notInvitedUsers = userFullInfos.stream()
.filter(w -> experts.stream().noneMatch(expert -> expert.getExpertId().equals(w.getUserId())))
.collect(Collectors.toList());
if (!notInvitedUsers.isEmpty()) {
result.addAll(notInvitedUsers);
if (result.size() >= count) {
return result.subList(0, count);
}
userFullInfos.removeAll(notInvitedUsers);
}
}
if (userFullInfos.size() == 0) {
return result;
}
int restCnt = Math.min(count - result.size(), userFullInfos.size());
if (userFullInfos.size() > restCnt) {
result.addAll(RandomUtil.randomEleList(userFullInfos, restCnt));
} else {
result.addAll(userFullInfos);
}
}
return result;
}

}

+ 12
- 101
pmapi/src/main/java/com/ningdatech/pmapi/meeting/manage/ExpertInviteManage.java View File

@@ -28,22 +28,20 @@ import com.ningdatech.pmapi.meta.model.entity.ExpertDictionary;
import com.ningdatech.pmapi.meta.model.entity.ExpertTag;
import com.ningdatech.pmapi.meta.service.IExpertDictionaryService;
import com.ningdatech.pmapi.meta.service.IExpertTagService;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.collections4.Predicate;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.RandomUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.ningdatech.pmapi.meeting.entity.enumeration.ExpertAttendStatusEnum.*;
import static com.ningdatech.pmapi.meeting.helper.ExpertInviteHelper.getExpertInviteRule;
import static com.ningdatech.pmapi.meeting.helper.ExpertRandomInviteAlgorithm.inviteGroupByCompany;

/**
* <p>
@@ -54,7 +52,7 @@ import static com.ningdatech.pmapi.meeting.helper.ExpertInviteHelper.getExpertIn
* @since 18:05 2022/8/8
*/
@Component
@AllArgsConstructor
@RequiredArgsConstructor
public class ExpertInviteManage {

private final IExpertDictionaryService expertDictionaryService;
@@ -69,6 +67,9 @@ public class ExpertInviteManage {
private final IExpertAvoidCompanyService expertAvoidCompanyService;
private final YxtCallOrSmsHelper yxtCallOrSmsHelper;

@Value("#{randomInviteProperties.recentMeetingCount}")
private Integer recentMeetingCount;

private static final Predicate<Collection<?>> COLL_EMPTY = (coll) -> coll != null && coll.isEmpty();

private LambdaQueryWrapper<ExpertUserFullInfo> buildBaseExpertQuery() {
@@ -322,7 +323,8 @@ public class ExpertInviteManage {
}
Map<String, List<ExpertUserFullInfo>> userGroupByUnit = CollUtils.group(userInfoList, ExpertUserFullInfo::getCompanyUniqCode);
result.setTotal(userGroupByUnit.size());
result.setExperts(inviteGroupByCompany(userGroupByUnit, randomRule.getCount()));

result.setExperts(inviteGroupByCompany(userGroupByUnit, expertsByRecentMeeting(), randomRule.getCount()));
return result;
}

@@ -431,15 +433,15 @@ public class ExpertInviteManage {
}
Map<String, List<ExpertUserFullInfo>> userGroupByUnit = CollUtils.group(userFullInfos, ExpertUserFullInfo::getCompanyUniqCode);
result.setTotal(userGroupByUnit.size());
result.setExperts(inviteGroupByCompany(userGroupByUnit, count));
result.setExperts(inviteGroupByCompany(userGroupByUnit, expertsByRecentMeeting(), count));
return result;
}

private List<List<MeetingExpert>> selectMeetingExpertByCount() {
private List<List<MeetingExpert>> expertsByRecentMeeting() {
LambdaQueryWrapper<Meeting> query = Wrappers.lambdaQuery(Meeting.class)
.select(Meeting::getId)
.orderByDesc(Meeting::getCreateOn)
.last("limit " + 5);
.last("limit " + recentMeetingCount);
List<Long> meetingIds = CollUtils.fieldList(meetingService.list(query), Meeting::getId);
if (meetingIds.isEmpty()) {
return Collections.emptyList();
@@ -453,97 +455,6 @@ public class ExpertInviteManage {
}

/**
* 每个单位只抽取一人
*
* @param expertGroupByUnit 需要抽取的人
* @param count 抽取数量
* @return 抽取到的专家信息
* @author WendyYang
**/
private List<ExpertUserFullInfo> inviteGroupByCompany(Map<String, List<ExpertUserFullInfo>> expertGroupByUnit, Integer count) {
if (MapUtils.isEmpty(expertGroupByUnit)) {
return Collections.emptyList();
}
List<List<MeetingExpert>> meetingExperts = selectMeetingExpertByCount();
if (meetingExperts.isEmpty()) {
return expertGroupByUnit.values().stream()
.map(expertUsers -> expertUsers.get(RandomUtils.nextInt(0, expertUsers.size())))
.limit(count).collect(Collectors.toList());
} else {
List<ExpertUserFullInfo> result = new ArrayList<>();
List<String> keySet = new ArrayList<>(expertGroupByUnit.keySet());
for (int i = 0; i < count; i++) {
String key = keySet.get(RandomUtils.nextInt(0, keySet.size()));
List<ExpertUserFullInfo> expertUserFullInfos = expertGroupByUnit.get(key);
for (List<MeetingExpert> expertList : meetingExperts) {
List<ExpertUserFullInfo> tempList = expertUserFullInfos.stream()
.filter(w -> expertList.stream().noneMatch(expert -> expert.getExpertId().equals(w.getUserId())))
.collect(Collectors.toList());
if (!tempList.isEmpty()) {
result.add(tempList.get(RandomUtils.nextInt(0, tempList.size())));
break;
} else if (meetingExperts.indexOf(expertList) == (meetingExperts.size() - 1)) {
result.add(expertUserFullInfos.get(RandomUtils.nextInt(0, expertUserFullInfos.size())));
}
}
if (result.size() < count) {
keySet.remove(key);
if (keySet.isEmpty()) {
break;
}
}
}
return result;
}
}

private List<ExpertUserFullInfo> inviteWithoutCompany(List<ExpertUserFullInfo> userFullInfos, Integer count) {
List<ExpertUserFullInfo> result = new ArrayList<>();
List<List<MeetingExpert>> meetingExpertList = selectMeetingExpertByCount();
if (meetingExpertList.isEmpty()) {
for (int i = 0; i < count; i++) {
int randomIndex = RandomUtils.nextInt(0, userFullInfos.size());
result.add(userFullInfos.remove(randomIndex));
if (userFullInfos.size() == 0) {
break;
}
}
} else {
for (List<MeetingExpert> meetingExperts : meetingExpertList) {
List<ExpertUserFullInfo> unSelectedUsers = userFullInfos.stream()
.filter(w -> meetingExperts.stream().noneMatch(expert -> expert.getExpertId().equals(w.getUserId())))
.collect(Collectors.toList());
if (!unSelectedUsers.isEmpty()) {
result.addAll(unSelectedUsers);
if (result.size() >= count) {
return result.subList(0, count);
}
userFullInfos.removeAll(unSelectedUsers);
}
}
if (userFullInfos.size() == 0) {
return result;
}
int restCount = Math.min(count - result.size(), userFullInfos.size());
int groupCount = userFullInfos.size() / restCount;
if (userFullInfos.size() > restCount) {
Stream.iterate(0, t -> t + 1).limit(restCount)
.forEach(t -> {
int start = t * groupCount;
int end = start + groupCount;
if (end >= groupCount * restCount) {
end = userFullInfos.size();
}
result.add(userFullInfos.get(RandomUtils.nextInt(start, end)));
});
} else {
result.addAll(userFullInfos);
}
}
return result;
}

/**
* 专家抽取(会议创建时抽取)
*
* @param randomRules 随机抽取规则


+ 46
- 51
pmapi/src/main/java/com/ningdatech/pmapi/meeting/task/ExpertInviteTask.java View File

@@ -39,13 +39,13 @@ import java.time.Duration;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;


/**
@@ -67,7 +67,6 @@ public class ExpertInviteTask {
private static final Duration EXPIRE_TIME = Duration.ofDays(60);

private final CachePlusOps cachePlusOps;
@Qualifier("expertInviteScheduler")
@Resource(name = "expertInviteScheduler")
private ThreadPoolTaskScheduler scheduler;
private final IMeetingExpertService meetingExpertService;
@@ -82,13 +81,13 @@ public class ExpertInviteTask {
*/
private static final ConcurrentMap<Long, ScheduledFuture<?>> INVITE_TASK_MAP = new ConcurrentHashMap<>();

public ExpertInviteTask currProxy() {
private ExpertInviteTask currProxy() {
return (ExpertInviteTask) AopContext.currentProxy();
}

private CacheHashKey getCacheKey(Long meetingId) {
String meetingIdStr = meetingId == null ? null : meetingId.toString();
return new CacheHashKey(MEETING_ID_INVITE_RANDOM, meetingIdStr, EXPIRE_TIME);
String field = meetingId == null ? null : meetingId.toString();
return new CacheHashKey(MEETING_ID_INVITE_RANDOM, field, EXPIRE_TIME);
}

@PostConstruct
@@ -97,13 +96,13 @@ public class ExpertInviteTask {
log.warn("随机邀请已关闭……");
return;
}
initInviteTaskAfterAppStarted();
initInviteTaskAfterStarted();
}

/**
* 项目重启之后重新初始化邀请任务
*/
private void initInviteTaskAfterAppStarted() {
private void initInviteTaskAfterStarted() {
Map<Long, InviteCacheDTO> caches = cachePlusOps.hGetAll(getCacheKey(null));
if (MapUtils.isEmpty(caches)) {
log.info("暂无需要初始化的抽取会议信息");
@@ -111,9 +110,9 @@ public class ExpertInviteTask {
}
Integer inviteDelay = properties.getInviteDelay();
for (InviteCacheDTO cache : caches.values()) {
Boolean invitedRefused = cache.getInvitedRefused();
Boolean reInvite = cache.getReInvite();
LocalDateTime tsTime = cache.getTaskStartTime();
boolean added = addInviteTask(cache.getMeetingId(), true, inviteDelay, invitedRefused, tsTime);
boolean added = addInviteTask(cache.getMeetingId(), true, inviteDelay, reInvite, tsTime);
if (!added) {
cachePlusOps.hDel(getCacheKey(cache.getMeetingId()));
}
@@ -135,7 +134,7 @@ public class ExpertInviteTask {
LambdaQueryWrapper<MeetingExpert> query = Wrappers.lambdaQuery(MeetingExpert.class)
.select(MeetingExpert::getRuleId, MeetingExpert::getStatus)
.in(MeetingExpert::getRuleId, ruleMap.keySet())
.in(MeetingExpert::getStatus, ExpertAttendStatusEnum.AGREED.getCode());
.eq(MeetingExpert::getStatus, ExpertAttendStatusEnum.AGREED.getCode());
List<MeetingExpert> experts = meetingExpertService.list(query);
if (experts.isEmpty()) {
return Boolean.TRUE;
@@ -153,23 +152,20 @@ public class ExpertInviteTask {
/**
* 唤醒某个会议的抽取任务
*
* @param meetingId 会议ID
* @param invitedRefused 是否可邀请已拒绝的专家
* @param meetingId 会议ID
* @param reInvite 是否可邀请已拒绝的专家
* @author WendyYang
**/
public void notifyInviteTask(Long meetingId, boolean... invitedRefused) {
boolean tmpInvitedRefused = true;
if (ArrayUtil.isNotEmpty(invitedRefused)) {
tmpInvitedRefused = invitedRefused[0];
}
public void notifyInviteTask(Long meetingId, boolean... reInvite) {
boolean tmpReInvite = !ArrayUtil.isNotEmpty(reInvite) || reInvite[0];
if (!INVITE_TASK_MAP.containsKey(meetingId)) {
if (addInviteTask(meetingId, false, properties.getInviteDelay(), tmpInvitedRefused, LocalDateTime.now())) {
if (addInviteTask(meetingId, false, properties.getInviteDelay(), tmpReInvite, LocalDateTime.now())) {
log.info("重置会议的随机抽取状态:{}", meetingId);
LambdaUpdateWrapper<Meeting> update = Wrappers.lambdaUpdate(Meeting.class);
update.set(Meeting::getInviteStatus, false);
update.eq(Meeting::getId, meetingId);
meetingService.update(update);
InviteCacheDTO cacheVal = InviteCacheDTO.of(meetingId, tmpInvitedRefused, LocalDateTime.now());
InviteCacheDTO cacheVal = InviteCacheDTO.of(meetingId, tmpReInvite, LocalDateTime.now());
cachePlusOps.hSet(getCacheKey(meetingId), cacheVal);
}
}
@@ -178,23 +174,24 @@ public class ExpertInviteTask {
/**
* 添加专家抽取校验任务
*
* @param meetingId 会议ID
* @param checked 是否前置校验
* @param delayedMinutes 延迟执行时间
* @param invitedRefused 是否可以邀请被拒绝的专家
* @param meetingId 会议ID
* @param checked 是否前置校验
* @param delayTime 延迟执行时间
* @param reInvite 是否可以邀请被拒绝的专家
* @param tsTime 任务启动时间
* @return 是否添加任务成功
* @author WendyYang
**/
public boolean addInviteTask(Long meetingId, boolean checked, int delayedMinutes, boolean invitedRefused, LocalDateTime tsTime) {
private boolean addInviteTask(Long meetingId, boolean checked, int delayTime, boolean reInvite, LocalDateTime tsTime) {
if (checked && !inviteCountCheck(meetingId)) {
// 如果抽取数量满足直接返回
return Boolean.FALSE;
}
Instant startTime = LocalDateTime.now().plusMinutes(delayedMinutes).atZone(ZoneId.systemDefault()).toInstant();
Instant startTime = LocalDateTime.now().plusMinutes(delayTime).atZone(ZoneId.systemDefault()).toInstant();
ScheduledFuture<?> future = scheduler.scheduleAtFixedRate(() -> {
ExpertInviteTask bean = SpringContextHolder.getBean(ExpertInviteTask.class);
try {
bean.invite(meetingId, invitedRefused, tsTime);
bean.invite(meetingId, reInvite, tsTime);
} catch (Exception e) {
log.error("执行专家邀请任务异常:{}", meetingId, e);
}
@@ -207,14 +204,14 @@ public class ExpertInviteTask {
/**
* 创建会议时添加抽取任务
*
* @param meetingId 会议ID
* @param delayedMinutes 延迟时间
* @param tsTime 开始时间
* @param meetingId 会议ID
* @param delayTime 延迟时间
* @param tsTime 开始时间
* @author WendyYang
**/
public void addInviteTaskByMeetingCreate(Long meetingId, int delayedMinutes, LocalDateTime tsTime) {
public void addInviteTaskByMeetingCreate(Long meetingId, int delayTime, LocalDateTime tsTime) {
Assert.isTrue(properties.getEnable(), "随机邀请已关闭");
addInviteTask(meetingId, false, delayedMinutes, false, tsTime);
addInviteTask(meetingId, false, delayTime, false, tsTime);
InviteCacheDTO cacheVal = InviteCacheDTO.of(meetingId, false, tsTime);
cachePlusOps.hSet(getCacheKey(meetingId), cacheVal);
}
@@ -222,12 +219,12 @@ public class ExpertInviteTask {
/**
* 抽取过程
*
* @param meetingId 会议ID
* @param invitedRefused 是否可以邀请已拒绝的专家
* @param tsTime 任务开启时间
* @param meetingId 会议ID
* @param reInvite 是否可以邀请已拒绝的专家
* @param tsTime 任务开启时间
*/
@Transactional(rollbackFor = Exception.class)
public void invite(Long meetingId, Boolean invitedRefused, LocalDateTime tsTime) {
public void invite(Long meetingId, Boolean reInvite, LocalDateTime tsTime) {
log.info("开始进行专家后台抽取:{}", meetingId);
Meeting meeting = meetingService.getById(meetingId);
if (meeting.getStartTime().isBefore(LocalDateTime.now())) {
@@ -248,7 +245,7 @@ public class ExpertInviteTask {
List<MeetingExpert> tmpExperts = meetingExpertService.listExpertLastByMeetingId(meetingId);
Map<Long, MeetingExpert> expertMap = CollUtils.listToMap(tmpExperts, MeetingExpert::getExpertId);
// 统计通知中与同意参加专家数量
Map<Long, ExpertCntBO> countMap = countByAgree(expertMap);
Map<Long, ExpertCntBO> countMap = countByAttendStatus(expertMap);
ExpertCntBO cnt = countMap.getOrDefault(ruleId, ExpertCntBO.zeroInit());
int wouldAttendCnt = cnt.getAgreeCnt() + cnt.getNoticeCnt();
if (wouldAttendCnt == value.getCount()) {
@@ -259,7 +256,7 @@ public class ExpertInviteTask {
}
int needInviteCnt = value.getCount() - wouldAttendCnt;
ExpertChooseDTO expertChoose = expertInviteManage.expertReplaceByRandomRule(avoidRule, value,
tmpExperts, needInviteCnt, msTime, meTime, tsTime, invitedRefused);
tmpExperts, needInviteCnt, msTime, meTime, tsTime, reInvite);

if (expertChoose.getTotal() > 0) {
List<MeetingExpert> expertMeetings = CollUtils.convert(expertChoose.getExperts(), w -> {
@@ -297,20 +294,18 @@ public class ExpertInviteTask {

//==================================================================================================================

private Map<Long, ExpertCntBO> countByAgree(Map<Long, MeetingExpert> expertMap) {
return expertMap.entrySet().stream()
.collect(Collectors.groupingBy(w -> w.getValue().getRuleId(),
Collectors.collectingAndThen(Collectors.mapping(Map.Entry::getValue, Collectors.toList()), w -> {
ExpertCntBO cnt = ExpertCntBO.zeroInit();
for (MeetingExpert expert : w) {
if (ExpertAttendStatusEnum.AGREED.eq(expert.getStatus())) {
cnt.incrAgreeCnt();
} else if (ExpertAttendStatusEnum.NOTICING.eq(expert.getStatus())) {
cnt.incrNoticeCnt();
}
}
return cnt;
})));
private Map<Long, ExpertCntBO> countByAttendStatus(Map<Long, MeetingExpert> expertMap) {
Map<Long, ExpertCntBO> cntMap = new HashMap<>(8);
expertMap.values().forEach(w -> {
Long ruleId = w.getRuleId();
ExpertCntBO cnt = cntMap.computeIfAbsent(ruleId, k -> ExpertCntBO.zeroInit());
if (ExpertAttendStatusEnum.AGREED.eq(w.getStatus())) {
cnt.incrAgreeCnt();
} else if (ExpertAttendStatusEnum.NOTICING.eq(w.getStatus())) {
cnt.incrNoticeCnt();
}
});
return cntMap;
}

@Data


+ 5
- 0
pmapi/src/main/java/com/ningdatech/pmapi/meeting/task/RandomInviteProperties.java View File

@@ -37,4 +37,9 @@ public class RandomInviteProperties {
* 会议抽取完成通知 管理员下发会议通知
*/
private Integer meetingInviteCompleteNoticeRate = 1;

/**
* 近期会议数量(以此来降低专家抽中间隔)
*/
private Integer recentMeetingCount = 5;
}

Loading…
Cancel
Save