diff --git a/orgmanax-infra/pom.xml b/orgmanax-infra/pom.xml index a4e24bf..d5331bc 100644 --- a/orgmanax-infra/pom.xml +++ b/orgmanax-infra/pom.xml @@ -125,5 +125,10 @@ msg-center-api-v2 1.0.1-SNAPSHOT + + + com.xuxueli + xxl-job-core + \ No newline at end of file diff --git a/orgmanax-infra/src/main/java/cn/axzo/orgmanax/infra/client/RpcWrapper.java b/orgmanax-infra/src/main/java/cn/axzo/orgmanax/infra/client/RpcWrapper.java index 3a87ad0..45dc765 100644 --- a/orgmanax-infra/src/main/java/cn/axzo/orgmanax/infra/client/RpcWrapper.java +++ b/orgmanax-infra/src/main/java/cn/axzo/orgmanax/infra/client/RpcWrapper.java @@ -4,6 +4,7 @@ import cn.axzo.foundation.exception.BusinessException; import cn.axzo.foundation.result.ApiResult; import cn.axzo.foundation.util.TraceUtils; import cn.axzo.orgmanax.common.config.BizResultCode; +import cn.azxo.framework.common.model.CommonResponse; import cn.hutool.core.convert.Convert; import cn.hutool.http.HttpStatus; import com.alibaba.fastjson.JSON; @@ -39,6 +40,23 @@ public class RpcWrapper { return wrapApiResult(supplier, desc, params, true); } + /** + * common res 返回 + * @param t + * @return + * @param + */ + public static T commonRes(Supplier> t) { + CommonResponse 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 返回 * diff --git a/orgmanax-infra/src/main/java/cn/axzo/orgmanax/infra/client/workspace/WorkspaceGateway.java b/orgmanax-infra/src/main/java/cn/axzo/orgmanax/infra/client/workspace/WorkspaceGateway.java index 3ffa262..8ad227d 100644 --- a/orgmanax-infra/src/main/java/cn/axzo/orgmanax/infra/client/workspace/WorkspaceGateway.java +++ b/orgmanax-infra/src/main/java/cn/axzo/orgmanax/infra/client/workspace/WorkspaceGateway.java @@ -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.WorkspaceDetailResp; 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.azxo.framework.common.logger.MethodAroundLog; +import cn.hutool.core.collection.CollUtil; +import com.google.common.collect.ImmutableList; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import java.util.Collection; +import java.util.Collections; 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 public class WorkspaceGateway { + private static final ImmutableList AUTO_LEAVE_CONFIG_TYPES = ImmutableList.of( + WorkspaceSceneConfigType.WORKER_AUTO_LEAVE, + WorkspaceSceneConfigType.WORKER_AUTO_LEAVE_DAYS + ); + private final WorkspaceV2Api workspaceV2Api; private final ParticipatingUnitV2Api participatingUnitV2Api; + private final WorkspaceSceneConfigApi workspaceSceneConfigApi; /** * 更新和新增 @@ -62,4 +82,55 @@ public class WorkspaceGateway { public List listParticipatingUnits(ParticipatingUnitListReq unitListReq) { return RpcWrapper.wrapApiResult(() -> participatingUnitV2Api.list(unitListReq)); } + + /** + * 查询项目相关配置项 + * + * @param request 入参 + * @return 配置项列表 + */ + @MethodAroundLog(source = "orgmanax", target = "workspace", value = "查询项目相关配置项") + public List listConfigs(WorkspaceSceneConfigListReq request) { + return RpcWrapper.commonRes(() -> workspaceSceneConfigApi.listConfigs(request)); + } + + /** + * 获取人员自动离场的天数配置(连续多少天没有打卡记录) + * + * @param workspaceIds 项目id列表 + * @return 配置项的map + */ + @MethodAroundLog(source = "orgmanax", target = "workspace", value = "获取人员自动离场的天数配置") + public Map listAutoLeaveDaysConfig(Collection workspaceIds) { + if (CollUtil.isEmpty(workspaceIds)) { + return Collections.emptyMap(); + } + WorkspaceSceneConfigListReq request = WorkspaceSceneConfigListReq.builder() + .workspaceIds(CollUtil.distinct(workspaceIds)) + .types(AUTO_LEAVE_CONFIG_TYPES) + .build(); + // list + List sceneConfigs = listConfigs(request); + if (CollUtil.isEmpty(sceneConfigs)) { + return Collections.emptyMap(); + } + // grouping + Map> groupingByWorkspaceId = sceneConfigs.stream() + .collect(Collectors.groupingBy(SceneConfigRes::getWorkspaceId)); + // mapper function + Function, 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)); + } } diff --git a/orgmanax-infra/src/main/java/cn/axzo/orgmanax/infra/dao/nodeuser/repository/NodeUserQueryRepository.java b/orgmanax-infra/src/main/java/cn/axzo/orgmanax/infra/dao/nodeuser/repository/NodeUserQueryRepository.java index 5c98268..513dda7 100644 --- a/orgmanax-infra/src/main/java/cn/axzo/orgmanax/infra/dao/nodeuser/repository/NodeUserQueryRepository.java +++ b/orgmanax-infra/src/main/java/cn/axzo/orgmanax/infra/dao/nodeuser/repository/NodeUserQueryRepository.java @@ -18,7 +18,12 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; 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 { @@ -52,6 +57,8 @@ public interface NodeUserQueryRepository { private Long id; @CriteriaField(field = "id", operator = Operator.GT) private Long idGt; + @CriteriaField(field = "id", operator = Operator.LT) + private Long idLt; @CriteriaField(field = "id", operator = Operator.IN) private Collection ids; @@ -156,6 +163,8 @@ public interface NodeUserQueryRepository { */ @CriteriaField private Long workspaceId; + @CriteriaField(field = "workspaceId", operator = Operator.GT) + private Long workspaceIdGt; @CriteriaField(field = "workspaceId", operator = Operator.IN) private Collection workspaceIds; diff --git a/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/cooperateship/foundation/CooperateShipFoundationService.java b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/cooperateship/foundation/CooperateShipFoundationService.java index 4f41b66..f5e027a 100644 --- a/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/cooperateship/foundation/CooperateShipFoundationService.java +++ b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/cooperateship/foundation/CooperateShipFoundationService.java @@ -1,11 +1,11 @@ package cn.axzo.orgmanax.server.cooperateship.foundation; 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 java.util.Collection; import java.util.List; -import java.util.Set; import java.util.function.Function; public interface CooperateShipFoundationService { @@ -76,4 +76,12 @@ public interface CooperateShipFoundationService { * 创建协同关系通用方法 */ SaasCooperateShip create(CooperateShipCreator creator); + + /** + * 协同关系通用查询 + * + * @param req 入参 + * @return 协同关系节点列表 + */ + List list(ListReq req); } diff --git a/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/cooperateship/foundation/impl/CooperateShipFoundationServiceImpl.java b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/cooperateship/foundation/impl/CooperateShipFoundationServiceImpl.java index dc65725..130f65e 100644 --- a/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/cooperateship/foundation/impl/CooperateShipFoundationServiceImpl.java +++ b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/cooperateship/foundation/impl/CooperateShipFoundationServiceImpl.java @@ -217,4 +217,9 @@ public class CooperateShipFoundationServiceImpl implements CooperateShipFoundati return savedCooperateShip; } + @Override + public List list(CooperateShipQueryRepository.ListReq req) { + return cooperateShipQueryRepository.list(req); + } + } diff --git a/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/nodeuser/foundation/NodeUserExtraFoundationService.java b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/nodeuser/foundation/NodeUserExtraFoundationService.java new file mode 100644 index 0000000..1b5ed62 --- /dev/null +++ b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/nodeuser/foundation/NodeUserExtraFoundationService.java @@ -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 page(ListReq req); + + /** + * 批量设置用户标签 + * + * @param idTagsMap key:id, value:标签集合 + */ + void updateTagsMap(Map> idTagsMap); +} diff --git a/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/nodeuser/foundation/NodeUserFoundationService.java b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/nodeuser/foundation/NodeUserFoundationService.java index 9806e56..6cf3bd2 100644 --- a/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/nodeuser/foundation/NodeUserFoundationService.java +++ b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/nodeuser/foundation/NodeUserFoundationService.java @@ -1,8 +1,11 @@ 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.resp.SearchEntNodeUserResp; 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.NodeUserDelete; import cn.axzo.orgmanax.server.nodeuser.foundation.req.NodeUserUpdate; @@ -55,4 +58,12 @@ public interface NodeUserFoundationService { * @return */ List searchEntUser(SearchEntNodeUserReq req); + + /** + * 通用分页查询 + * + * @param req 入参 + * @return node_user列表 + */ + PageResp page(ListReq req); } diff --git a/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/nodeuser/foundation/impl/NodeUserExtraFoundationServiceImpl.java b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/nodeuser/foundation/impl/NodeUserExtraFoundationServiceImpl.java new file mode 100644 index 0000000..275e2ce --- /dev/null +++ b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/nodeuser/foundation/impl/NodeUserExtraFoundationServiceImpl.java @@ -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 page(ListReq req) { + return queryRepository.page(req); + } + + @Override + public void updateTagsMap(Map> idTagsMap) { + upsertRepository.updateTagsMap(idTagsMap); + } +} diff --git a/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/nodeuser/foundation/impl/NodeUserFoundationServiceImpl.java b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/nodeuser/foundation/impl/NodeUserFoundationServiceImpl.java index 1ce877f..148ee8d 100644 --- a/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/nodeuser/foundation/impl/NodeUserFoundationServiceImpl.java +++ b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/nodeuser/foundation/impl/NodeUserFoundationServiceImpl.java @@ -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.producer.EventProducer; import cn.axzo.foundation.exception.Axssert; +import cn.axzo.foundation.page.PageResp; import cn.axzo.orgmanax.common.config.BizResultCode; import cn.axzo.orgmanax.dto.common.util.NumberUtil; 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.mapper.OrganizationalNodeUserMapper; 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.orgjob.entity.OrgJob; 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.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; /** @@ -258,6 +265,11 @@ public class NodeUserFoundationServiceImpl implements NodeUserFoundationService return organizationalNodeUserMapper.searchEntUser(req); } + @Override + public PageResp page(ListReq req) { + return nodeUserQueryRepository.page(req); + } + private Long resolveWorkspaceId(OrganizationalNode node) { if (Objects.equals(node.getNodeType(), NodeUserTypeEnum.TEAM.getValue())) { return 0L; diff --git a/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/orguser/config/PersonTagConfig.java b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/orguser/config/PersonTagConfig.java new file mode 100644 index 0000000..45685c6 --- /dev/null +++ b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/orguser/config/PersonTagConfig.java @@ -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); + } + } +} diff --git a/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/orguser/xxl/AutoAddWorkerLeavedTagJob.java b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/orguser/xxl/AutoAddWorkerLeavedTagJob.java new file mode 100644 index 0000000..49030bd --- /dev/null +++ b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/orguser/xxl/AutoAddWorkerLeavedTagJob.java @@ -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 { + + private static final String LOG_PREFIX = "[ADD_WORKER_LEAVED_TAG ] "; + + private static final ImmutableList INCLUDE_WORKSPACE_TYPES = ImmutableList.of(Workspace.WorkspaceTypeEnum.GENERAL_PROJECT.getValue()); + private static final ImmutableList INCLUDE_COOPERATE_TYPES = ImmutableList.of(CooperateShipTypeEnum.PROJ_TEAM.getCode(), + CooperateShipTypeEnum.PROJ_GROUP.getCode()); + private static final ImmutableList INCLUDE_STATUS = ImmutableList.of(CooperateShipStatusEnum.PRESENT.getCode()); + + @Resource + private NodeUserFoundationService nodeUserFoundationService; + @Resource + private CooperateShipFoundationService cooperateShipFoundationService; + + private Context context; + + @Override + @XxlJob("autoAddWorkerLeavedTagJob") + public ReturnT 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 scrollList(long scrollIndex) { + ListReq page = new ListReq(); + page.setIdLt(scrollIndex); + page.setIdentityType(IdentityType.WORKER.getCode()); + page.setWorkspaceIdGt(0L); + PageResp 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 records, List clockedPersonIds) { + Date joinedAtLe = context.getJoinedAtLe(workspaceId); + if (Objects.isNull(joinedAtLe)) { + return; + } + List 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> dataScope = cooperateShipFoundationService.list(listReq).stream() + .collect(Collectors.groupingBy(SaasCooperateShip::getWorkspaceId)); + context.setActiveProjectTeamAndGroups(dataScope); + } + + private void assembleWorkspaceConfig(Context context) { + if (context.invalid()) { + return; + } + Map workspaceDaysConfig = Maps.newHashMap(); + context.setWorkspaceDaysConfig(workspaceDaysConfig); + Set workspaceIds = context.getWorkspaceDaysConfig().keySet(); + List> partition = Lists.partition(Lists.newArrayList(workspaceIds), 20); + for (List sub : partition) { + Map autoLeaveDaysConfigMap = workspaceGateway.listAutoLeaveDaysConfig(sub); + if (CollUtil.isEmpty(autoLeaveDaysConfigMap)) { + continue; + } + workspaceDaysConfig.putAll(autoLeaveDaysConfigMap); + } + } + + private void addLeavedTag(Long workspaceId, List 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> activeProjectTeamAndGroups; + + /** + * 开关开启的项目id集合 + * key: workspaceId + * value: days + */ + private Map 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()); + } + } +} diff --git a/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/orguser/xxl/AutoClearPersonLeavedTagJob.java b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/orguser/xxl/AutoClearPersonLeavedTagJob.java new file mode 100644 index 0000000..34af79a --- /dev/null +++ b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/orguser/xxl/AutoClearPersonLeavedTagJob.java @@ -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 { + + private static final String LOG_PREFIX = "[CLEAR_PERSON_LEAVED_TAG] "; + + @Resource + private NodeUserExtraFoundationService nodeUserExtraFoundationService; + + private Context context; + + @Override + @XxlJob("autoClearPersonLeavedTagJob") + public ReturnT 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 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 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 records, List 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 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 workspaceDaysConfig = Maps.newHashMap(); + } +} diff --git a/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/orguser/xxl/BaseAutoOperatePersonLeavedTagJob.java b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/orguser/xxl/BaseAutoOperatePersonLeavedTagJob.java new file mode 100644 index 0000000..577d9fb --- /dev/null +++ b/orgmanax-server/src/main/java/cn/axzo/orgmanax/server/orguser/xxl/BaseAutoOperatePersonLeavedTagJob.java @@ -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 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> localCache = Maps.newHashMap(); + List 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 scrollList(long scrollIndex); + + abstract Long extractId(T record); + + abstract Long extractPersonId(T record); + + abstract Long extractWorkspaceId(T record); + + abstract void handle(Long workspaceId, List records, List clockedPersonIds); + + abstract Integer listAutoLeaveDaysConfig(Long workspaceId); + + private void cacheAndTrigger(Map> localCache, List records) { + // grouping by workspaceId + Map> 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> localCache, Long workspaceId, List sub) { + List 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> partitions = Lists.partition(cacheRecords, MAX_CNT_ONCE); + for (List 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 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 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 findClockedPersons(Long workspaceId, Integer days, List records) { + List personIds = CollUtil.map(records, this::extractPersonId, true); + if (CollUtil.isEmpty(personIds)) { + List 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 clockRecords = listClockRecords(workspaceId, days, personIds); + // extract personId + return CollUtil.map(clockRecords, AttendanceClockRecordResp::getPersonId, true); + } + + private List listClockRecords(Long workspaceId, Integer days, List 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); + } +}