feat(REQ-3714) 工人退场流程优化 - 工人自动打离场标签以及取消标签的XXL-JOB

This commit is contained in:
luofu 2025-03-12 09:49:45 +08:00
parent 59a0daf3bf
commit 2ad2378ab1
14 changed files with 790 additions and 3 deletions

View File

@ -125,5 +125,10 @@
<artifactId>msg-center-api-v2</artifactId> <artifactId>msg-center-api-v2</artifactId>
<version>1.0.1-SNAPSHOT</version> <version>1.0.1-SNAPSHOT</version>
</dependency> </dependency>
<!--xxl-job-->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -4,6 +4,7 @@ import cn.axzo.foundation.exception.BusinessException;
import cn.axzo.foundation.result.ApiResult; import cn.axzo.foundation.result.ApiResult;
import cn.axzo.foundation.util.TraceUtils; import cn.axzo.foundation.util.TraceUtils;
import cn.axzo.orgmanax.common.config.BizResultCode; import cn.axzo.orgmanax.common.config.BizResultCode;
import cn.azxo.framework.common.model.CommonResponse;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import cn.hutool.http.HttpStatus; import cn.hutool.http.HttpStatus;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
@ -39,6 +40,23 @@ public class RpcWrapper {
return wrapApiResult(supplier, desc, params, true); return wrapApiResult(supplier, desc, params, true);
} }
/**
* common res 返回
* @param t
* @return
* @param <T>
*/
public static <T> T commonRes(Supplier<CommonResponse<T>> t) {
CommonResponse<T> result = t.get();
if (result == null) {
throw new BusinessException(BizResultCode.RPC_ERROR);
}
if (!Objects.equals(result.getCode(), HttpStatus.HTTP_OK)) {
throw new BusinessException(Convert.toStr(result.getCode()), result.getMsg(), null);
}
return result.getData();
}
/** /**
* Api result 返回 * Api result 返回
* *

View File

@ -10,12 +10,26 @@ import cn.axzo.apollo.workspace.api.v2.workspace.req.WorkspaceUpsertReq;
import cn.axzo.apollo.workspace.api.v2.workspace.resp.WorkspaceDTO; import cn.axzo.apollo.workspace.api.v2.workspace.resp.WorkspaceDTO;
import cn.axzo.apollo.workspace.api.v2.workspace.resp.WorkspaceDetailResp; import cn.axzo.apollo.workspace.api.v2.workspace.resp.WorkspaceDetailResp;
import cn.axzo.apollo.workspace.api.v2.workspace.resp.WorkspaceUpsertResp; import cn.axzo.apollo.workspace.api.v2.workspace.resp.WorkspaceUpsertResp;
import cn.axzo.apollo.workspace.api.workspace.WorkspaceSceneConfigApi;
import cn.axzo.apollo.workspace.api.workspace.req.scene.WorkspaceSceneConfigListReq;
import cn.axzo.apollo.workspace.api.workspace.res.scene.SceneConfigRes;
import cn.axzo.apollo.workspace.common.enums.SceneConfigSwitch;
import cn.axzo.apollo.workspace.common.enums.WorkspaceSceneConfigType;
import cn.axzo.orgmanax.infra.client.RpcWrapper; import cn.axzo.orgmanax.infra.client.RpcWrapper;
import cn.azxo.framework.common.logger.MethodAroundLog;
import cn.hutool.core.collection.CollUtil;
import com.google.common.collect.ImmutableList;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;
/** /**
* *
@ -25,9 +39,15 @@ import java.util.List;
@Slf4j @Slf4j
public class WorkspaceGateway { public class WorkspaceGateway {
private static final ImmutableList<WorkspaceSceneConfigType> AUTO_LEAVE_CONFIG_TYPES = ImmutableList.of(
WorkspaceSceneConfigType.WORKER_AUTO_LEAVE,
WorkspaceSceneConfigType.WORKER_AUTO_LEAVE_DAYS
);
private final WorkspaceV2Api workspaceV2Api; private final WorkspaceV2Api workspaceV2Api;
private final ParticipatingUnitV2Api participatingUnitV2Api; private final ParticipatingUnitV2Api participatingUnitV2Api;
private final WorkspaceSceneConfigApi workspaceSceneConfigApi;
/** /**
* 更新和新增 * 更新和新增
@ -62,4 +82,55 @@ public class WorkspaceGateway {
public List<ParticipatingUnitResp> listParticipatingUnits(ParticipatingUnitListReq unitListReq) { public List<ParticipatingUnitResp> listParticipatingUnits(ParticipatingUnitListReq unitListReq) {
return RpcWrapper.wrapApiResult(() -> participatingUnitV2Api.list(unitListReq)); return RpcWrapper.wrapApiResult(() -> participatingUnitV2Api.list(unitListReq));
} }
/**
* 查询项目相关配置项
*
* @param request 入参
* @return 配置项列表
*/
@MethodAroundLog(source = "orgmanax", target = "workspace", value = "查询项目相关配置项")
public List<SceneConfigRes> listConfigs(WorkspaceSceneConfigListReq request) {
return RpcWrapper.commonRes(() -> workspaceSceneConfigApi.listConfigs(request));
}
/**
* 获取人员自动离场的天数配置连续多少天没有打卡记录
*
* @param workspaceIds 项目id列表
* @return 配置项的map
*/
@MethodAroundLog(source = "orgmanax", target = "workspace", value = "获取人员自动离场的天数配置")
public Map<Long, Integer> listAutoLeaveDaysConfig(Collection<Long> workspaceIds) {
if (CollUtil.isEmpty(workspaceIds)) {
return Collections.emptyMap();
}
WorkspaceSceneConfigListReq request = WorkspaceSceneConfigListReq.builder()
.workspaceIds(CollUtil.distinct(workspaceIds))
.types(AUTO_LEAVE_CONFIG_TYPES)
.build();
// list
List<SceneConfigRes> sceneConfigs = listConfigs(request);
if (CollUtil.isEmpty(sceneConfigs)) {
return Collections.emptyMap();
}
// grouping
Map<Long, List<SceneConfigRes>> groupingByWorkspaceId = sceneConfigs.stream()
.collect(Collectors.groupingBy(SceneConfigRes::getWorkspaceId));
// mapper function
Function<List<SceneConfigRes>, SceneConfigRes> mapper = list -> {
if (list.stream().anyMatch(e -> WorkspaceSceneConfigType.WORKER_AUTO_LEAVE.name().equals(e.getType())
&& Objects.equals(e.getValue(), SceneConfigSwitch.OPEN.getValue()))) {
return list.stream()
.filter(e -> WorkspaceSceneConfigType.WORKER_AUTO_LEAVE_DAYS.name().equals(e.getType()))
.findFirst().orElse(null);
}
// switch not exist or closed
return null;
};
return groupingByWorkspaceId.values().stream()
.map(mapper)
.filter(Objects::nonNull)
.collect(Collectors.toMap(SceneConfigRes::getWorkspaceId, e -> Integer.valueOf(e.getValue()), (oldVal, newVal) -> oldVal));
}
} }

View File

@ -18,7 +18,12 @@ import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder; import lombok.experimental.SuperBuilder;
import java.util.*; import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.Set;
public interface NodeUserQueryRepository { public interface NodeUserQueryRepository {
@ -52,6 +57,8 @@ public interface NodeUserQueryRepository {
private Long id; private Long id;
@CriteriaField(field = "id", operator = Operator.GT) @CriteriaField(field = "id", operator = Operator.GT)
private Long idGt; private Long idGt;
@CriteriaField(field = "id", operator = Operator.LT)
private Long idLt;
@CriteriaField(field = "id", operator = Operator.IN) @CriteriaField(field = "id", operator = Operator.IN)
private Collection<Long> ids; private Collection<Long> ids;
@ -156,6 +163,8 @@ public interface NodeUserQueryRepository {
*/ */
@CriteriaField @CriteriaField
private Long workspaceId; private Long workspaceId;
@CriteriaField(field = "workspaceId", operator = Operator.GT)
private Long workspaceIdGt;
@CriteriaField(field = "workspaceId", operator = Operator.IN) @CriteriaField(field = "workspaceId", operator = Operator.IN)
private Collection<Long> workspaceIds; private Collection<Long> workspaceIds;

View File

@ -1,11 +1,11 @@
package cn.axzo.orgmanax.server.cooperateship.foundation; package cn.axzo.orgmanax.server.cooperateship.foundation;
import cn.axzo.orgmanax.infra.dao.cooperateship.entity.SaasCooperateShip; import cn.axzo.orgmanax.infra.dao.cooperateship.entity.SaasCooperateShip;
import cn.axzo.orgmanax.infra.dao.cooperateship.repository.CooperateShipQueryRepository.ListReq;
import cn.axzo.orgmanax.server.cooperateship.foundation.dto.CooperateShipCreator; import cn.axzo.orgmanax.server.cooperateship.foundation.dto.CooperateShipCreator;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
public interface CooperateShipFoundationService { public interface CooperateShipFoundationService {
@ -76,4 +76,12 @@ public interface CooperateShipFoundationService {
* 创建协同关系通用方法 * 创建协同关系通用方法
*/ */
SaasCooperateShip create(CooperateShipCreator creator); SaasCooperateShip create(CooperateShipCreator creator);
/**
* 协同关系通用查询
*
* @param req 入参
* @return 协同关系节点列表
*/
List<SaasCooperateShip> list(ListReq req);
} }

View File

@ -217,4 +217,9 @@ public class CooperateShipFoundationServiceImpl implements CooperateShipFoundati
return savedCooperateShip; return savedCooperateShip;
} }
@Override
public List<SaasCooperateShip> list(CooperateShipQueryRepository.ListReq req) {
return cooperateShipQueryRepository.list(req);
}
} }

View File

@ -0,0 +1,31 @@
package cn.axzo.orgmanax.server.nodeuser.foundation;
import cn.axzo.foundation.page.PageResp;
import cn.axzo.orgmanax.infra.dao.nodeuser.repository.NodeUserExtraQueryRepository.ListReq;
import cn.axzo.orgmanax.infra.dao.nodeuser.repository.NodeUserExtraQueryRepository.NodeUserExtraResp;
import java.util.Map;
import java.util.Set;
/**
* @author luofu
* @version 1.0
* @date 2025/3/11
*/
public interface NodeUserExtraFoundationService {
/***
* 分页查询
*
* @param req 入参
* @return node_user_extra 列表
*/
PageResp<NodeUserExtraResp> page(ListReq req);
/**
* 批量设置用户标签
*
* @param idTagsMap key:id, value:标签集合
*/
void updateTagsMap(Map<Long, Set<String>> idTagsMap);
}

View File

@ -1,8 +1,11 @@
package cn.axzo.orgmanax.server.nodeuser.foundation; package cn.axzo.orgmanax.server.nodeuser.foundation;
import cn.axzo.foundation.page.PageResp;
import cn.axzo.orgmanax.dto.nodeuser.req.SearchEntNodeUserReq; import cn.axzo.orgmanax.dto.nodeuser.req.SearchEntNodeUserReq;
import cn.axzo.orgmanax.dto.nodeuser.resp.SearchEntNodeUserResp; import cn.axzo.orgmanax.dto.nodeuser.resp.SearchEntNodeUserResp;
import cn.axzo.orgmanax.infra.dao.nodeuser.entity.OrganizationalNodeUser; import cn.axzo.orgmanax.infra.dao.nodeuser.entity.OrganizationalNodeUser;
import cn.axzo.orgmanax.infra.dao.nodeuser.repository.NodeUserQueryRepository.ListReq;
import cn.axzo.orgmanax.infra.dao.nodeuser.repository.NodeUserQueryRepository.NodeUserResp;
import cn.axzo.orgmanax.server.nodeuser.foundation.req.NodeUserCreate; import cn.axzo.orgmanax.server.nodeuser.foundation.req.NodeUserCreate;
import cn.axzo.orgmanax.server.nodeuser.foundation.req.NodeUserDelete; import cn.axzo.orgmanax.server.nodeuser.foundation.req.NodeUserDelete;
import cn.axzo.orgmanax.server.nodeuser.foundation.req.NodeUserUpdate; import cn.axzo.orgmanax.server.nodeuser.foundation.req.NodeUserUpdate;
@ -55,4 +58,12 @@ public interface NodeUserFoundationService {
* @return * @return
*/ */
List<SearchEntNodeUserResp> searchEntUser(SearchEntNodeUserReq req); List<SearchEntNodeUserResp> searchEntUser(SearchEntNodeUserReq req);
/**
* 通用分页查询
*
* @param req 入参
* @return node_user列表
*/
PageResp<NodeUserResp> page(ListReq req);
} }

View File

@ -0,0 +1,38 @@
package cn.axzo.orgmanax.server.nodeuser.foundation.impl;
import cn.axzo.foundation.page.PageResp;
import cn.axzo.orgmanax.infra.dao.nodeuser.repository.NodeUserExtraQueryRepository;
import cn.axzo.orgmanax.infra.dao.nodeuser.repository.NodeUserExtraQueryRepository.ListReq;
import cn.axzo.orgmanax.infra.dao.nodeuser.repository.NodeUserExtraQueryRepository.NodeUserExtraResp;
import cn.axzo.orgmanax.infra.dao.nodeuser.repository.NodeUserExtraUpsertRepository;
import cn.axzo.orgmanax.server.nodeuser.foundation.NodeUserExtraFoundationService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Map;
import java.util.Set;
/**
* @author luofu
* @version 1.0
* @date 2025/3/11
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class NodeUserExtraFoundationServiceImpl implements NodeUserExtraFoundationService {
private final NodeUserExtraQueryRepository queryRepository;
private final NodeUserExtraUpsertRepository upsertRepository;
@Override
public PageResp<NodeUserExtraResp> page(ListReq req) {
return queryRepository.page(req);
}
@Override
public void updateTagsMap(Map<Long, Set<String>> idTagsMap) {
upsertRepository.updateTagsMap(idTagsMap);
}
}

View File

@ -5,6 +5,7 @@ import cn.axzo.apollo.workspace.api.v2.workspace.resp.WorkspaceDetailResp;
import cn.axzo.foundation.event.support.Event; import cn.axzo.foundation.event.support.Event;
import cn.axzo.foundation.event.support.producer.EventProducer; import cn.axzo.foundation.event.support.producer.EventProducer;
import cn.axzo.foundation.exception.Axssert; import cn.axzo.foundation.exception.Axssert;
import cn.axzo.foundation.page.PageResp;
import cn.axzo.orgmanax.common.config.BizResultCode; import cn.axzo.orgmanax.common.config.BizResultCode;
import cn.axzo.orgmanax.dto.common.util.NumberUtil; import cn.axzo.orgmanax.dto.common.util.NumberUtil;
import cn.axzo.orgmanax.dto.nodeuser.enums.NodeUserTypeEnum; import cn.axzo.orgmanax.dto.nodeuser.enums.NodeUserTypeEnum;
@ -21,6 +22,8 @@ import cn.axzo.orgmanax.infra.dao.node.repository.NodeQueryRepository;
import cn.axzo.orgmanax.infra.dao.nodeuser.entity.OrganizationalNodeUser; import cn.axzo.orgmanax.infra.dao.nodeuser.entity.OrganizationalNodeUser;
import cn.axzo.orgmanax.infra.dao.nodeuser.mapper.OrganizationalNodeUserMapper; import cn.axzo.orgmanax.infra.dao.nodeuser.mapper.OrganizationalNodeUserMapper;
import cn.axzo.orgmanax.infra.dao.nodeuser.repository.NodeUserQueryRepository; import cn.axzo.orgmanax.infra.dao.nodeuser.repository.NodeUserQueryRepository;
import cn.axzo.orgmanax.infra.dao.nodeuser.repository.NodeUserQueryRepository.ListReq;
import cn.axzo.orgmanax.infra.dao.nodeuser.repository.NodeUserQueryRepository.NodeUserResp;
import cn.axzo.orgmanax.infra.dao.nodeuser.repository.NodeUserUpsertRepository; import cn.axzo.orgmanax.infra.dao.nodeuser.repository.NodeUserUpsertRepository;
import cn.axzo.orgmanax.infra.dao.orgjob.entity.OrgJob; import cn.axzo.orgmanax.infra.dao.orgjob.entity.OrgJob;
import cn.axzo.orgmanax.infra.dao.orgjob.repository.OrgJobQueryRepository; import cn.axzo.orgmanax.infra.dao.orgjob.repository.OrgJobQueryRepository;
@ -46,7 +49,11 @@ import org.apache.commons.lang3.ObjectUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.*; import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@ -258,6 +265,11 @@ public class NodeUserFoundationServiceImpl implements NodeUserFoundationService
return organizationalNodeUserMapper.searchEntUser(req); return organizationalNodeUserMapper.searchEntUser(req);
} }
@Override
public PageResp<NodeUserResp> page(ListReq req) {
return nodeUserQueryRepository.page(req);
}
private Long resolveWorkspaceId(OrganizationalNode node) { private Long resolveWorkspaceId(OrganizationalNode node) {
if (Objects.equals(node.getNodeType(), NodeUserTypeEnum.TEAM.getValue())) { if (Objects.equals(node.getNodeType(), NodeUserTypeEnum.TEAM.getValue())) {
return 0L; return 0L;

View File

@ -0,0 +1,59 @@
package cn.axzo.orgmanax.server.orguser.config;
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
import java.io.Serializable;
/**
* @author luofu
* @version 1.0
* @description 人员标签配置
* @date 2025/3/6
*/
@Data
@RefreshScope
@Configuration
@ConfigurationProperties(prefix = "person-tag")
public class PersonTagConfig {
/**
* 离场标签
*/
private TagNode leaveTagNode = TagNode.of("station", "leave proj");
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class TagNode implements Serializable {
private static final long serialVersionUID = -8933389377639107477L;
private String nodeCode;
private String valueCode;
public String format(String spliter) {
return this.nodeCode + spliter + this.valueCode;
}
private static TagNode of(String nodeCode, String valueCode) {
return TagNode.builder()
.nodeCode(nodeCode)
.valueCode(valueCode)
.build();
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}
}

View File

@ -0,0 +1,212 @@
package cn.axzo.orgmanax.server.orguser.xxl;
import cn.axzo.foundation.page.PageResp;
import cn.axzo.orgmanax.dto.common.IdentityType;
import cn.axzo.orgmanax.dto.cooperateship.enums.CooperateShipStatusEnum;
import cn.axzo.orgmanax.dto.cooperateship.enums.CooperateShipTypeEnum;
import cn.axzo.orgmanax.dto.nodeuser.enums.TagOperateEnum;
import cn.axzo.orgmanax.dto.nodeuser.req.TagOperateReq;
import cn.axzo.orgmanax.infra.client.workspace.dto.Workspace;
import cn.axzo.orgmanax.infra.dao.cooperateship.entity.SaasCooperateShip;
import cn.axzo.orgmanax.infra.dao.cooperateship.repository.CooperateShipQueryRepository;
import cn.axzo.orgmanax.infra.dao.nodeuser.repository.NodeUserQueryRepository.ListReq;
import cn.axzo.orgmanax.infra.dao.nodeuser.repository.NodeUserQueryRepository.NodeUserResp;
import cn.axzo.orgmanax.server.cooperateship.foundation.CooperateShipFoundationService;
import cn.axzo.orgmanax.server.nodeuser.foundation.NodeUserFoundationService;
import cn.hutool.core.collection.CollUtil;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author luofu
* @version 1.0
* @description 自动退场OR标记为离场人员的JOB
* @date 2025/3/6
*/
@Slf4j
@Component
public class AutoAddWorkerLeavedTagJob extends BaseAutoOperatePersonLeavedTagJob<NodeUserResp> {
private static final String LOG_PREFIX = "[ADD_WORKER_LEAVED_TAG ] ";
private static final ImmutableList<Integer> INCLUDE_WORKSPACE_TYPES = ImmutableList.of(Workspace.WorkspaceTypeEnum.GENERAL_PROJECT.getValue());
private static final ImmutableList<Integer> INCLUDE_COOPERATE_TYPES = ImmutableList.of(CooperateShipTypeEnum.PROJ_TEAM.getCode(),
CooperateShipTypeEnum.PROJ_GROUP.getCode());
private static final ImmutableList<Integer> INCLUDE_STATUS = ImmutableList.of(CooperateShipStatusEnum.PRESENT.getCode());
@Resource
private NodeUserFoundationService nodeUserFoundationService;
@Resource
private CooperateShipFoundationService cooperateShipFoundationService;
private Context context;
@Override
@XxlJob("autoAddWorkerLeavedTagJob")
public ReturnT<String> execute(String param) {
// init context of job
initContext();
if (context.invalid()) {
// failed to init context.
error("failed to init context.");
return ReturnT.FAIL;
}
// start to execute
doExecute(param);
return ReturnT.SUCCESS;
}
@Override
String logPrefix() {
return LOG_PREFIX;
}
@Override
LocalDateTime startDateTimeOfJobExecution() {
return context.getStartDateTime();
}
@Override
List<NodeUserResp> scrollList(long scrollIndex) {
ListReq page = new ListReq();
page.setIdLt(scrollIndex);
page.setIdentityType(IdentityType.WORKER.getCode());
page.setWorkspaceIdGt(0L);
PageResp<NodeUserResp> pageResult = nodeUserFoundationService.page(page);
return Optional.ofNullable(pageResult.getData()).orElseGet(Collections::emptyList);
}
@Override
Long extractId(NodeUserResp record) {
return record.getId();
}
@Override
Long extractPersonId(NodeUserResp record) {
return record.getPersonId();
}
@Override
Long extractWorkspaceId(NodeUserResp record) {
return record.getWorkspaceId();
}
@Override
void handle(Long workspaceId, List<NodeUserResp> records, List<Long> clockedPersonIds) {
Date joinedAtLe = context.getJoinedAtLe(workspaceId);
if (Objects.isNull(joinedAtLe)) {
return;
}
List<Long> withoutClockedPersonIds = records.stream()
// filter join_at condition
.filter(e -> Objects.nonNull(e.getJoinAt()) && joinedAtLe.after(e.getJoinAt()))
.map(NodeUserResp::getPersonId)
.filter(personId -> !clockedPersonIds.contains(personId))
.distinct().collect(Collectors.toList());
if (CollUtil.isEmpty(withoutClockedPersonIds)) {
// all person contains clocked record.
return;
}
// auto add leaved tag
addLeavedTag(workspaceId, withoutClockedPersonIds);
}
@Override
Integer listAutoLeaveDaysConfig(Long workspaceId) {
return context.getWorkspaceDaysConfig().get(workspaceId);
}
private void initContext() {
context = new Context();
assembleDataScope(context);
assembleWorkspaceConfig(context);
}
private void assembleDataScope(Context context) {
CooperateShipQueryRepository.ListReq listReq = new CooperateShipQueryRepository.ListReq();
listReq.setWorkspaceTypes(INCLUDE_WORKSPACE_TYPES);
listReq.setIncludeCooperateTypes(INCLUDE_COOPERATE_TYPES);
listReq.setStatuses(INCLUDE_STATUS);
Map<Long, List<SaasCooperateShip>> dataScope = cooperateShipFoundationService.list(listReq).stream()
.collect(Collectors.groupingBy(SaasCooperateShip::getWorkspaceId));
context.setActiveProjectTeamAndGroups(dataScope);
}
private void assembleWorkspaceConfig(Context context) {
if (context.invalid()) {
return;
}
Map<Long, Integer> workspaceDaysConfig = Maps.newHashMap();
context.setWorkspaceDaysConfig(workspaceDaysConfig);
Set<Long> workspaceIds = context.getWorkspaceDaysConfig().keySet();
List<List<Long>> partition = Lists.partition(Lists.newArrayList(workspaceIds), 20);
for (List<Long> sub : partition) {
Map<Long, Integer> autoLeaveDaysConfigMap = workspaceGateway.listAutoLeaveDaysConfig(sub);
if (CollUtil.isEmpty(autoLeaveDaysConfigMap)) {
continue;
}
workspaceDaysConfig.putAll(autoLeaveDaysConfigMap);
}
}
private void addLeavedTag(Long workspaceId, List<Long> personIds) {
TagOperateReq param = new TagOperateReq();
param.setPersonIds(personIds);
param.setLoginWorkspaceId(workspaceId);
param.setTagNodeCode(personTagConfig.getLeaveTagNode().getNodeCode());
param.setTagValueCode(personTagConfig.getLeaveTagNode().getValueCode());
param.setOperateType(TagOperateEnum.ADD);
orgUserService.operateTag(param);
}
@Data
private static class Context {
private LocalDateTime startDateTime = LocalDateTime.now();
/**
* 在场的项目内班组和小组节点
* key: workspaceId
* value: 在场的项目内班组和小组节点列表
*/
private Map<Long, List<SaasCooperateShip>> activeProjectTeamAndGroups;
/**
* 开关开启的项目id集合
* key: workspaceId
* value: days
*/
private Map<Long, Integer> workspaceDaysConfig;
boolean invalid() {
return CollUtil.isEmpty(activeProjectTeamAndGroups);
}
Date getJoinedAtLe(Long workspaceId) {
Integer days = workspaceDaysConfig.get(workspaceId);
if (Objects.isNull(days)) {
return null;
}
LocalDateTime le = startDateTime.minusDays(days);
return Date.from(le.atZone(ZoneId.systemDefault()).toInstant());
}
}
}

View File

@ -0,0 +1,136 @@
package cn.axzo.orgmanax.server.orguser.xxl;
import cn.axzo.foundation.page.PageResp;
import cn.axzo.orgmanax.dto.nodeuser.enums.TagOperateEnum;
import cn.axzo.orgmanax.dto.nodeuser.req.TagOperateReq;
import cn.axzo.orgmanax.infra.dao.nodeuser.repository.NodeUserExtraQueryRepository;
import cn.axzo.orgmanax.infra.dao.nodeuser.repository.NodeUserExtraQueryRepository.NodeUserExtraResp;
import cn.axzo.orgmanax.server.nodeuser.foundation.NodeUserExtraFoundationService;
import cn.hutool.core.collection.CollUtil;
import com.google.common.collect.Maps;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
/**
* @author luofu
* @version 1.0
* @description 自动清除人员离场标签的JOB
* @date 2025/3/6
*/
@Slf4j
@Component
public class AutoClearPersonLeavedTagJob extends BaseAutoOperatePersonLeavedTagJob<NodeUserExtraResp> {
private static final String LOG_PREFIX = "[CLEAR_PERSON_LEAVED_TAG] ";
@Resource
private NodeUserExtraFoundationService nodeUserExtraFoundationService;
private Context context;
@Override
@XxlJob("autoClearPersonLeavedTagJob")
public ReturnT<String> execute(String param) {
// init context
context = new Context();
doExecute(param);
return ReturnT.SUCCESS;
}
@Override
String logPrefix() {
return LOG_PREFIX;
}
@Override
LocalDateTime startDateTimeOfJobExecution() {
return context.getStartDateTime();
}
@Override
List<NodeUserExtraResp> scrollList(long scrollIndex) {
String tag = personTagConfig.getLeaveTagNode().format(":");
NodeUserExtraQueryRepository.ListReq page = new NodeUserExtraQueryRepository.ListReq();
page.setIdLt(scrollIndex);
page.setTag(tag);
page.setPageSize(MAX_CNT_ONCE);
page.setSort(Collections.singletonList(sortDesc("id")));
PageResp<NodeUserExtraQueryRepository.NodeUserExtraResp> pageResult = nodeUserExtraFoundationService.page(page);
return Optional.ofNullable(pageResult.getData()).orElseGet(Collections::emptyList);
}
@Override
Long extractId(NodeUserExtraResp record) {
return record.getId();
}
@Override
Long extractPersonId(NodeUserExtraResp record) {
return record.getPersonId();
}
@Override
Long extractWorkspaceId(NodeUserExtraResp record) {
return record.getWorkspaceId();
}
@Override
void handle(Long workspaceId, List<NodeUserExtraResp> records, List<Long> clockedPersonIds) {
if (CollUtil.isEmpty(clockedPersonIds)) {
// none clocked person.
return;
}
TagOperateReq param = new TagOperateReq();
param.setPersonIds(clockedPersonIds);
param.setLoginWorkspaceId(workspaceId);
param.setTagNodeCode(personTagConfig.getLeaveTagNode().getNodeCode());
param.setTagValueCode(personTagConfig.getLeaveTagNode().getValueCode());
param.setOperateType(TagOperateEnum.REMOVE);
orgUserService.operateTag(param);
}
@Override
Integer listAutoLeaveDaysConfig(Long workspaceId) {
// fetch from local cache first.
Integer days = context.getWorkspaceDaysConfig().get(workspaceId);
if (Objects.nonNull(days)) {
return days;
}
// fetch from attendance service.
Map<Long, Integer> map = workspaceGateway.listAutoLeaveDaysConfig(Collections.singletonList(workspaceId));
if (CollUtil.isEmpty(map)) {
return null;
}
// cache the config of this workspace
context.getWorkspaceDaysConfig().putAll(map);
// return days config
return context.getWorkspaceDaysConfig().get(workspaceId);
}
@Data
private static class Context {
/**
* start time of job execute
*/
private LocalDateTime startDateTime = LocalDateTime.now();
/**
* days configuration for add person tag of workspace.
* key: workspaceId
* value: days
*/
private Map<Long, Integer> workspaceDaysConfig = Maps.newHashMap();
}
}

View File

@ -0,0 +1,172 @@
package cn.axzo.orgmanax.server.orguser.xxl;
import cn.axzo.foundation.page.IPageReq;
import cn.axzo.orgmanax.infra.client.attendance.AttendanceRecordClient;
import cn.axzo.orgmanax.infra.client.attendance.dto.AttendanceClockRecordListReq;
import cn.axzo.orgmanax.infra.client.attendance.dto.AttendanceClockRecordResp;
import cn.axzo.orgmanax.infra.client.workspace.WorkspaceGateway;
import cn.axzo.orgmanax.server.orguser.config.PersonTagConfig;
import cn.axzo.orgmanax.server.orguser.service.OrgUserService;
import cn.hutool.core.collection.CollUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.log.XxlJobLogger;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* @author luofu
* @version 1.0
* @date 2025/3/6
*/
@Slf4j
public abstract class BaseAutoOperatePersonLeavedTagJob<T> extends IJobHandler {
static final int MAX_CNT_ONCE = 4;
@Resource
PersonTagConfig personTagConfig;
@Resource
OrgUserService orgUserService;
@Resource
AttendanceRecordClient attendanceRecordApiClient;
@Resource
WorkspaceGateway workspaceGateway;
void doExecute(String param) {
long scrollIndex = Long.MAX_VALUE;
info("start to scan org_user.");
// key: workspaceId value: records
Map<Long, List<T>> localCache = Maps.newHashMap();
List<T> records = scrollList(scrollIndex);
while (CollUtil.isNotEmpty(records)) {
// the min id of records.
scrollIndex = records.stream().mapToLong(this::extractId).min().orElse(0);
// cache first, then trigger if enough.
cacheAndTrigger(localCache, records);
// the next
records = scrollList(scrollIndex);
}
// scan cached records
localCache.forEach(this::trigger);
info("completed to scan org_user.");
}
abstract String logPrefix();
abstract LocalDateTime startDateTimeOfJobExecution();
abstract List<T> scrollList(long scrollIndex);
abstract Long extractId(T record);
abstract Long extractPersonId(T record);
abstract Long extractWorkspaceId(T record);
abstract void handle(Long workspaceId, List<T> records, List<Long> clockedPersonIds);
abstract Integer listAutoLeaveDaysConfig(Long workspaceId);
private void cacheAndTrigger(Map<Long, List<T>> localCache, List<T> records) {
// grouping by workspaceId
Map<Long, List<T>> groupingByWorkspaceId = records.stream().collect(Collectors.groupingBy(this::extractWorkspaceId));
// scan for cache and trigger
groupingByWorkspaceId.forEach((k, v) -> cacheAndTrigger(localCache, k, v));
}
private void cacheAndTrigger(Map<Long, List<T>> localCache, Long workspaceId, List<T> sub) {
List<T> cacheRecords = localCache.computeIfAbsent(workspaceId, k -> Lists.newArrayList());
// merge cur records and cached records
cacheRecords.addAll(sub);
if (cacheRecords.size() < MAX_CNT_ONCE) {
// return if not matched
return;
}
// remove from local cache
localCache.remove(workspaceId);
// deal matched partitions and cache not matched
List<List<T>> partitions = Lists.partition(cacheRecords, MAX_CNT_ONCE);
for (List<T> partition : partitions) {
if (partition.size() < MAX_CNT_ONCE) {
// cache
localCache.put(workspaceId, partition);
continue;
}
// trigger
trigger(workspaceId, partition);
}
}
public static String sortDesc(String field) {
return field + IPageReq.SORT_DELIMITER + IPageReq.SORT_DESC;
}
private void trigger(Long workspaceId, List<T> records) {
try {
// list days config of cur workspace.
Integer days = listAutoLeaveDaysConfig(workspaceId);
if (Objects.isNull(days)) {
info("lack of days config for this workspace. workspaceId:[{}]", workspaceId);
return;
}
// in try ... catch block for continue the next ...
// find persons which contains clocked record during specified time.
List<Long> personIds = findClockedPersons(workspaceId, days, records);
info("start to remove person tag of workspace. [{}]", workspaceId);
// begin to handle the personIds.
handle(workspaceId, records, personIds);
info("completed to remove person tag of workspace. [{}]", workspaceId);
} catch (Exception e) {
error("broke out some exception. workspaceId:[{}]", workspaceId, e);
}
}
private List<Long> findClockedPersons(Long workspaceId, Integer days, List<T> records) {
List<Long> personIds = CollUtil.map(records, this::extractPersonId, true);
if (CollUtil.isEmpty(personIds)) {
List<Long> ids = CollUtil.map(records, this::extractId, true);
error("invalid records, lack of personId. workspaceId:[{}], recordIds:{}", workspaceId, JSON.toJSONString(ids));
return Collections.emptyList();
}
// list persons which clocked during the range of specified time.
List<AttendanceClockRecordResp> clockRecords = listClockRecords(workspaceId, days, personIds);
// extract personId
return CollUtil.map(clockRecords, AttendanceClockRecordResp::getPersonId, true);
}
private List<AttendanceClockRecordResp> listClockRecords(Long workspaceId, Integer days, List<Long> personIds) {
LocalDateTime to = startDateTimeOfJobExecution();
LocalDateTime from = to.minusDays(days);
AttendanceClockRecordListReq request = new AttendanceClockRecordListReq();
request.setPersonIds(personIds);
request.setWorkspaceId(workspaceId);
request.setClockAtFrom(Date.from(from.atZone(ZoneOffset.systemDefault()).toInstant()));
request.setClockAtTo(Date.from(to.atZone(ZoneOffset.systemDefault()).toInstant()));
return attendanceRecordApiClient.listClockRecords(request);
}
void info(String msgFormat, Object... args) {
msgFormat = logPrefix() + msgFormat;
log.info(msgFormat, args);
XxlJobLogger.log(msgFormat, args);
}
void error(String msgFormat, Object... args) {
msgFormat = logPrefix() + msgFormat;
log.error(msgFormat, args);
XxlJobLogger.log(msgFormat, args);
}
}