Merge remote-tracking branch 'origin/master'

pull/66/head
shao1121353141 2023-10-05 10:23:09 +08:00
commit d2aa510913
159 changed files with 1401 additions and 3550 deletions

View File

@ -1,34 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
eiam-alert - Employee Identity and Access Management
Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.topiam</groupId>
<artifactId>eiam</artifactId>
<version>1.0.2-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eiam-alert</artifactId>
<packaging>pom</packaging>
</project>

View File

@ -23,12 +23,15 @@ import java.io.Serializable;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import cn.topiam.employee.support.security.userdetails.UserType;
import lombok.Builder;
import lombok.Data;
import lombok.NonNull;
/**
* Actor
*
* @author TopIAM
* Created by support@topiam.cn on 2022/11/5 23:30
*/
@ -36,32 +39,32 @@ import lombok.NonNull;
@Builder
public class Actor implements Serializable {
public static final String ACTOR_ID = "actor.id";
public static final String ACTOR_TYPE = "actor.type";
public static final String ACTOR_ID = "actor.id";
public static final String ACTOR_TYPE = "actor.type";
public static final String ACTOR_AUTH_TYPE = "actor.auth_type.keyword";
public static final String ACTOR_AUTH_TYPE = "actor.auth_type.keyword";
@Serial
private static final long serialVersionUID = -1144169992714000310L;
private static final long serialVersionUID = -1144169992714000310L;
/**
* ID
*/
@NonNull
@Field(type = FieldType.Keyword, name = "id")
private String id;
private String id;
/**
*
*/
@NonNull
@Field(type = FieldType.Keyword, name = "type")
private cn.topiam.employee.support.security.userdetails.UserType type;
private UserType type;
/**
*
*/
@Field(type = FieldType.Keyword, name = "auth_type")
private String authType;
private String authType;
}

View File

@ -1,94 +0,0 @@
/*
* eiam-audit - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.audit.entity;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.Setting;
import lombok.Builder;
import lombok.Data;
/**
* elasticsearch
*
* @author TopIAM
* Created by support@topiam.cn on 2022/10/13 23:22
*/
@Data
@Builder
@Document(indexName = "#{@auditDynamicIndexName.getIndexName()}")
@Setting(replicas = 0)
public class AuditElasticSearchEntity implements Serializable {
@Serial
private static final long serialVersionUID = 6589338521638519634L;
@Id
@Field(type = FieldType.Keyword, name = "id")
private String id;
/**
* Request Id
*/
@Field(type = FieldType.Keyword, name = "request_id")
private String requestId;
/**
* Session Id
*/
@Field(type = FieldType.Keyword, name = "session_id")
private String sessionId;
/**
*
*/
@Field(type = FieldType.Object, name = "actor")
private Actor actor;
/**
*
*/
@Field(type = FieldType.Object, name = "event")
private Event event;
/**
*
*/
@Field(type = FieldType.Object, name = "target")
private List<Target> targets;
/**
* UserAgent
*/
@Field(type = FieldType.Object, name = "user_agent")
private UserAgent userAgent;
/**
*
*/
@Field(type = FieldType.Object, name = "geo_location")
private GeoLocation geoLocation;
}

View File

@ -0,0 +1,93 @@
/*
* eiam-audit - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.audit.event;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson2.JSONObject;
import cn.topiam.employee.audit.entity.*;
import cn.topiam.employee.audit.repository.AuditRepository;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2021/9/12 22:49
*/
@Component
@Async
public class AuditEventListener implements ApplicationListener<AuditEvent> {
private final Logger logger = LoggerFactory.getLogger(AuditEventListener.class);
/**
* onApplicationEvent
*
* @param auditEvent {@link AuditEvent}
*/
@Override
public void onApplicationEvent(@NonNull AuditEvent auditEvent) {
Event event = auditEvent.getEvent();
Actor actor = auditEvent.getActor();
List<Target> target = auditEvent.getTargets();
GeoLocation geoLocation = auditEvent.getGeoLocation();
UserAgent userAgent = auditEvent.getUserAgent();
//保存数据库
AuditEntity entity = new AuditEntity();
try {
entity.setRequestId(auditEvent.getRequestId());
entity.setSessionId(auditEvent.getSessionId());
//事件
entity.setEventType(event.getType());
entity.setEventContent(event.getContent());
entity.setEventParam(event.getParam());
entity.setEventStatus(event.getStatus());
entity.setEventResult(event.getResult());
entity.setEventTime(event.getTime());
//操作目标
entity.setTargets(target);
entity.setGeoLocation(geoLocation);
entity.setUserAgent(userAgent);
entity.setActorId(actor.getId());
entity.setActorType(actor.getType());
entity.setActorAuthType(actor.getAuthType());
auditRepository.save(entity);
} catch (Exception e) {
logger.error("Audit record saving failed: {}", JSONObject.toJSONString(entity), e);
}
}
/**
* AuditRepository
*/
private final AuditRepository auditRepository;
public AuditEventListener(AuditRepository auditRepository) {
this.auditRepository = auditRepository;
}
}

View File

@ -23,8 +23,7 @@ import java.util.Map;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
@ -37,7 +36,6 @@ import com.google.common.collect.Maps;
import cn.topiam.employee.audit.entity.*;
import cn.topiam.employee.audit.enums.EventStatus;
import cn.topiam.employee.audit.event.type.EventType;
import cn.topiam.employee.audit.mq.AuditMessagePublisher;
import cn.topiam.employee.support.context.ServletContextHelp;
import cn.topiam.employee.support.geo.GeoLocationService;
import cn.topiam.employee.support.security.authentication.WebAuthenticationDetails;
@ -47,8 +45,6 @@ import cn.topiam.employee.support.trace.TraceUtils;
import cn.topiam.employee.support.util.IpUtils;
import cn.topiam.employee.support.web.useragent.UserAgentParser;
import lombok.AllArgsConstructor;
import jakarta.servlet.http.HttpServletRequest;
import static cn.topiam.employee.support.util.StringUtils.replaceBlank;
@ -59,11 +55,8 @@ import static cn.topiam.employee.support.util.StringUtils.replaceBlank;
* Created by support@topiam.cn on 2021/8/1 21:04
*/
@Component
@AllArgsConstructor
public class AuditEventPublish {
private final Logger logger = LoggerFactory.getLogger(AuditEventPublish.class);
/**
*
*
@ -84,7 +77,7 @@ public class AuditEventPublish {
//封装操作人
Actor actor = getActor();
//Publish AuditEvent
auditMessagePublisher.sendAuditChangeMessage(new AuditEvent(TraceUtils.get(), ServletContextHelp.getSession().getId(), actor, event, userAgent, geoLocationModal, null));
applicationEventPublisher.publishEvent(new AuditEvent(TraceUtils.get(), ServletContextHelp.getSession().getId(), actor, event, userAgent, geoLocationModal, null));
//@formatter:on
}
@ -115,7 +108,7 @@ public class AuditEventPublish {
//封装操作人
Actor actor = getActor(authentication);
//Publish AuditEvent
auditMessagePublisher.sendAuditChangeMessage(new AuditEvent(TraceUtils.get(), ServletContextHelp.getSession().getId(), actor, event, userAgent, geoLocationModal, targets));
applicationEventPublisher.publishEvent(new AuditEvent(TraceUtils.get(), ServletContextHelp.getSession().getId(), actor, event, userAgent, geoLocationModal, targets));
//@formatter:on
}
@ -137,7 +130,7 @@ public class AuditEventPublish {
//封装用户代理
UserAgent userAgent = getUserAgent();
//Publish AuditEvent
auditMessagePublisher.sendAuditChangeMessage(new AuditEvent(TraceUtils.get(), ServletContextHelp.getSession().getId(), actor, event, userAgent, geoLocationModal, null));
applicationEventPublisher.publishEvent(new AuditEvent(TraceUtils.get(), ServletContextHelp.getSession().getId(), actor, event, userAgent, geoLocationModal, null));
//@formatter:on
}
@ -178,7 +171,7 @@ public class AuditEventPublish {
actor = getActor();
}
//Publish AuditEvent
auditMessagePublisher.sendAuditChangeMessage(new AuditEvent(TraceUtils.get(), ServletContextHelp.getSession().getId(), actor, event, userAgent, geoLocationModal, target));
applicationEventPublisher.publishEvent(new AuditEvent(TraceUtils.get(), ServletContextHelp.getSession().getId(), actor, event, userAgent, geoLocationModal, target));
//@formatter:on
}
@ -204,7 +197,7 @@ public class AuditEventPublish {
//封装操作人
Actor actor = getActor();
//Publish AuditEvent
auditMessagePublisher.sendAuditChangeMessage(new AuditEvent(TraceUtils.get(), ServletContextHelp.getSession().getId(), actor, event, userAgent, geoLocationModal, target));
applicationEventPublisher.publishEvent(new AuditEvent(TraceUtils.get(), ServletContextHelp.getSession().getId(), actor, event, userAgent, geoLocationModal, target));
//@formatter:on
}
@ -249,6 +242,9 @@ public class AuditEventPublish {
if (principal instanceof UserDetails) {
return ((UserDetails) principal).getId();
}
if (principal instanceof String) {
return (String) principal;
}
return null;
//@formatter:on
}
@ -384,13 +380,18 @@ public class AuditEventPublish {
}
/**
* AuditMessagePublisher
* ApplicationEventPublisher
*/
private final AuditMessagePublisher auditMessagePublisher;
private final ApplicationEventPublisher applicationEventPublisher;
/**
*
*/
private final GeoLocationService geoLocationService;
private final GeoLocationService geoLocationService;
public AuditEventPublish(ApplicationEventPublisher applicationEventPublisher,
GeoLocationService geoLocationService) {
this.applicationEventPublisher = applicationEventPublisher;
this.geoLocationService = geoLocationService;
}
}

View File

@ -78,6 +78,13 @@ public class AccountEventType {
public static Type MOVE_ORGANIZATION = new Type(
"eiam:event:account:move_organization", "移动组织", ORG_ACCOUNT_RESOURCE, List.of(ADMIN));
/**
*
*/
public static Type CREATE_ORGANIZATION_MEMBER = new Type(
"eiam:event:account:create_organization_member", "添加组织用户", ORG_ACCOUNT_RESOURCE,
List.of(ADMIN));
/**
*
*/

View File

@ -72,6 +72,11 @@ public enum EventType {
*/
MOVE_ORGANIZATION(AccountEventType.MOVE_ORGANIZATION),
/**
*
*/
CREATE_ORGANIZATION_MEMBER(AccountEventType.CREATE_ORGANIZATION_MEMBER),
/**
*
*/

View File

@ -1,137 +0,0 @@
/*
* eiam-audit - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.audit.mq;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson2.JSONObject;
import com.rabbitmq.client.Channel;
import cn.topiam.employee.audit.entity.*;
import cn.topiam.employee.audit.event.AuditEvent;
import cn.topiam.employee.audit.repository.AuditElasticSearchRepository;
import cn.topiam.employee.audit.repository.AuditRepository;
import cn.topiam.employee.support.exception.TopIamException;
import cn.topiam.employee.support.trace.TraceUtils;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import static cn.topiam.employee.audit.mq.AuditMessagePublisher.AUDIT_TOPIC;
import static cn.topiam.employee.support.trace.TraceAspect.TRACE_ID;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2023/5/30 23:12
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class AuditMessageListener {
/**
*
*
* @param message {@link Message}
* @param channel {@link Channel}
*/
@SneakyThrows
@RabbitListener(queues = AUDIT_TOPIC, ackMode = "MANUAL")
@RabbitHandler()
public String onMessage(Message message, Channel channel, @Payload AuditEvent auditEvent,
@Headers Map<String, Object> headers) throws TopIamException {
try {
//设置TraceId
TraceUtils.put(String.valueOf(headers.get(TRACE_ID)));
log.info("接收审计事件入参: [{}]", message);
Event event = auditEvent.getEvent();
Actor actor = auditEvent.getActor();
List<Target> target = auditEvent.getTargets();
GeoLocation geoLocation = auditEvent.getGeoLocation();
UserAgent userAgent = auditEvent.getUserAgent();
//保存数据库
AuditEntity entity = new AuditEntity();
Optional<AuditEntity> optional = auditRepository
.findByRequestId(auditEvent.getRequestId());
if (optional.isEmpty()) {
entity.setRequestId(auditEvent.getRequestId());
entity.setSessionId(auditEvent.getSessionId());
//事件
entity.setEventType(event.getType());
entity.setEventContent(event.getContent());
entity.setEventParam(event.getParam());
entity.setEventStatus(event.getStatus());
entity.setEventResult(event.getResult());
entity.setEventTime(event.getTime());
//操作目标
entity.setTargets(target);
entity.setGeoLocation(geoLocation);
entity.setUserAgent(userAgent);
entity.setActorId(actor.getId());
entity.setActorType(actor.getType());
auditRepository.save(entity);
} else {
entity = optional.get();
}
if (!Objects.isNull(entity.getId())) {
//保存 Elasticsearch
AuditElasticSearchEntity audit = AuditElasticSearchEntity.builder().build();
audit.setRequestId(auditEvent.getRequestId());
audit.setSessionId(auditEvent.getSessionId());
audit.setId(entity.getId().toString());
audit.setEvent(event);
audit.setTargets(target);
audit.setGeoLocation(geoLocation);
audit.setUserAgent(userAgent);
audit.setActor(actor);
auditElasticSearchRepository.save(audit);
}
log.info("处理审计事件成功:[{}]", message.getMessageProperties().getDeliveryTag());
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
return "处理审计事件成功";
} catch (Exception e) {
log.error("处理审计事件出现异常: MessageProperties: [{}], 审计内容:[{}]",
message.getMessageProperties(), JSONObject.toJSONString(auditEvent), e);
channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
return "处理审计事件失败";
}
}
/**
* AuditRepository
*/
private final AuditRepository auditRepository;
/**
* AuditElasticSearchRepository
*/
private final AuditElasticSearchRepository auditElasticSearchRepository;
}

View File

@ -1,85 +0,0 @@
/*
* eiam-audit - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.audit.mq;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.AsyncRabbitTemplate;
import org.springframework.amqp.rabbit.RabbitConverterFuture;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson2.JSONObject;
import cn.topiam.employee.audit.event.AuditEvent;
import cn.topiam.employee.support.trace.TraceUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import jakarta.annotation.PostConstruct;
import static cn.topiam.employee.support.trace.TraceAspect.TRACE_ID;
/**
* MQ
*
* @author TopIAM
* Created by support@topiam.cn on 2023/5/30 23:12
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class AuditMessagePublisher {
private final AsyncRabbitTemplate asyncRabbitTemplate;
private final AmqpAdmin amqpAdmin;
public final static String AUDIT_TOPIC = "audit";
@PostConstruct
public void init() {
TopicExchange topicExchange = new TopicExchange(AUDIT_TOPIC);
Queue queue = new Queue(AUDIT_TOPIC, true);
amqpAdmin.declareExchange(topicExchange);
amqpAdmin.declareQueue(queue);
amqpAdmin.declareBinding(BindingBuilder.bind(queue).to(topicExchange).with(AUDIT_TOPIC));
}
/**
*
*
* @param data {@link String}
*/
public void sendAuditChangeMessage(AuditEvent data) {
String traceId = TraceUtils.get();
log.info("发送审计消息, 审计内容:[{}]", JSONObject.toJSONString(data));
RabbitConverterFuture<Object> future = asyncRabbitTemplate.convertSendAndReceive(
AUDIT_TOPIC, AUDIT_TOPIC,
MessageBuilder.withPayload(data).setHeader(TRACE_ID, traceId).build());
future.whenComplete((result, ex) -> {
if (ex == null) {
log.info("发送审计消息成功,处理结果为:[{}]", result);
} else {
log.error("发送审计消息异常,审计内容:[{}]", JSONObject.toJSONString(data), ex);
}
});
}
}

View File

@ -0,0 +1,46 @@
/*
* eiam-audit - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.audit.repository;
import java.time.LocalDateTime;
import java.util.List;
import cn.topiam.employee.audit.event.type.EventType;
import cn.topiam.employee.audit.repository.result.AuditStatisticsResult;
import cn.topiam.employee.audit.repository.result.AuthnQuantityResult;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2022/10/2 02:53
*/
public interface AuditCustomizedRepository {
List<AuditStatisticsResult> authnHotProvider(EventType type, LocalDateTime startTime,
LocalDateTime endTime);
List<AuthnQuantityResult> authnQuantity(EventType type, LocalDateTime startTime,
LocalDateTime endTime, String dateFormat);
List<AuditStatisticsResult> appVisitRank(EventType type, LocalDateTime startTime,
LocalDateTime endTime);
List<AuditStatisticsResult> authnZone(EventType type, LocalDateTime startTime,
LocalDateTime endTime);
}

View File

@ -36,7 +36,7 @@ import cn.topiam.employee.support.repository.LogicDeleteRepository;
*/
@Repository
public interface AuditRepository extends LogicDeleteRepository<AuditEntity, Long>,
QuerydslPredicateExecutor<AuditEntity> {
QuerydslPredicateExecutor<AuditEntity>, AuditCustomizedRepository {
/**
*
@ -58,4 +58,8 @@ public interface AuditRepository extends LogicDeleteRepository<AuditEntity, Long
* @return {@link AuditEntity}
*/
Optional<AuditEntity> findByRequestId(String requestId);
@Query(value = "SELECT COUNT(*) FROM audit WHERE event_type = :type AND event_time BETWEEN :startTime AND :endTime", nativeQuery = true)
Long countByTypeAndTime(@Param("type") String type, @Param("startTime") LocalDateTime startTime,
@Param("endTime") LocalDateTime endTime);
}

View File

@ -0,0 +1,137 @@
/*
* eiam-audit - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.audit.repository.impl;
import java.time.LocalDateTime;
import java.util.List;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import cn.topiam.employee.audit.event.type.EventType;
import cn.topiam.employee.audit.repository.AuditCustomizedRepository;
import cn.topiam.employee.audit.repository.impl.mapper.AuditStatisticsResultMapper;
import cn.topiam.employee.audit.repository.impl.mapper.AuthnQuantityResultMapper;
import cn.topiam.employee.audit.repository.result.AuditStatisticsResult;
import cn.topiam.employee.audit.repository.result.AuthnQuantityResult;
import lombok.RequiredArgsConstructor;
/**
*
* @author TopIAM
* Created by support@topiam.cn on 2022/10/2 02:54
*/
@Repository
@RequiredArgsConstructor
public class AuditCustomizedRepositoryImpl implements AuditCustomizedRepository {
/**
* JdbcTemplate
*/
private final JdbcTemplate jdbcTemplate;
@Override
public List<AuditStatisticsResult> authnHotProvider(EventType type, LocalDateTime startTime,
LocalDateTime endTime) {
String sql = """
SELECT
actor_auth_type AS key_,
COUNT(*) AS count_
FROM
audit
WHERE
event_type = ?
AND event_time BETWEEN ?
AND ?
GROUP BY
actor_auth_type
""";
return jdbcTemplate.query(sql, new AuditStatisticsResultMapper(), type.getCode(), startTime,
endTime);
}
@Override
public List<AuthnQuantityResult> authnQuantity(EventType type, LocalDateTime startTime,
LocalDateTime endTime, String dateFormat) {
String sql = """
SELECT
DATE_FORMAT( event_time, ? ) AS name_,
COUNT(*) AS count_,
event_status AS status_
FROM
audit
WHERE
event_type = ?
AND event_time BETWEEN ?
AND ?
GROUP BY
DATE_FORMAT( event_time, ? ),
event_status
""";
return jdbcTemplate.query(sql, new AuthnQuantityResultMapper(), dateFormat, type.getCode(),
startTime, endTime, dateFormat);
}
@Override
public List<AuditStatisticsResult> appVisitRank(EventType type, LocalDateTime startTime,
LocalDateTime endTime) {
String sql = """
SELECT
JSON_EXTRACT( target_, '$[0].id' ) AS key_,
COUNT(*) AS count_
FROM
audit
WHERE
event_type = ?
AND event_time BETWEEN ?
AND ?
GROUP BY
JSON_EXTRACT(
target_,
'$[0].id'
)
""";
return jdbcTemplate.query(sql, new AuditStatisticsResultMapper(), type.getCode(), startTime,
endTime);
}
@Override
public List<AuditStatisticsResult> authnZone(EventType type, LocalDateTime startTime,
LocalDateTime endTime) {
String sql = """
SELECT
JSON_EXTRACT( target_, '$.provinceCode' ) AS key_,
COUNT(*) AS count_
FROM
audit
WHERE
event_type = ?
AND event_time BETWEEN ?
AND ?
GROUP BY
JSON_EXTRACT(
target_,
'$.provinceCode'
)
""";
return jdbcTemplate.query(sql, new AuditStatisticsResultMapper(), type.getCode(), startTime,
endTime);
}
}

View File

@ -0,0 +1,57 @@
/*
* eiam-audit - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.audit.repository.impl.mapper;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.lang.NonNull;
import cn.topiam.employee.audit.repository.result.AuditStatisticsResult;
/**
* @author TopIAM
* Created by support@topiam.cn on 2023/10/04 22:25
*/
@SuppressWarnings("DuplicatedCode")
public class AuditStatisticsResultMapper implements RowMapper<AuditStatisticsResult> {
/**
* Implementations must implement this method to map each row of data
* in the ResultSet. This method should not call {@code next()} on
* the ResultSet; it is only supposed to map values of the current row.
*
* @param rs the ResultSet to map (pre-initialized for the current row)
* @param rowNum the number of the current row
* @return the result object for the current row (may be {@code null})
* @throws SQLException if an SQLException is encountered getting
* column values (that is, there's no need to catch SQLException)
*/
@Override
public AuditStatisticsResult mapRow(@NonNull ResultSet rs, int rowNum) throws SQLException {
//@formatter:off
AuditStatisticsResult user = new AuditStatisticsResult();
if (StringUtils.isNoneBlank(rs.getString("key_"))) {
user.setKey(rs.getString("key_").replace("\"", ""));
}
user.setCount(rs.getLong("count_"));
//@formatter:on
return user;
}
}

View File

@ -0,0 +1,55 @@
/*
* eiam-audit - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.audit.repository.impl.mapper;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.lang.NonNull;
import cn.topiam.employee.audit.repository.result.AuthnQuantityResult;
/**
* @author TopIAM
* Created by support@topiam.cn on 2023/10/04 22:25
*/
@SuppressWarnings("DuplicatedCode")
public class AuthnQuantityResultMapper implements RowMapper<AuthnQuantityResult> {
/**
* Implementations must implement this method to map each row of data
* in the ResultSet. This method should not call {@code next()} on
* the ResultSet; it is only supposed to map values of the current row.
*
* @param rs the ResultSet to map (pre-initialized for the current row)
* @param rowNum the number of the current row
* @return the result object for the current row (may be {@code null})
* @throws SQLException if an SQLException is encountered getting
* column values (that is, there's no need to catch SQLException)
*/
@Override
public AuthnQuantityResult mapRow(@NonNull ResultSet rs, int rowNum) throws SQLException {
//@formatter:off
AuthnQuantityResult user = new AuthnQuantityResult();
user.setName(rs.getString("name_"));
user.setCount(rs.getLong("count_"));
user.setStatus(rs.getString("status_"));
//@formatter:on
return user;
}
}

View File

@ -0,0 +1,18 @@
/*
* eiam-audit - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.audit.repository.impl;

View File

@ -15,21 +15,35 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.audit.repository;
package cn.topiam.employee.audit.repository.result;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
import java.io.Serializable;
import cn.topiam.employee.audit.entity.AuditElasticSearchEntity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import io.swagger.v3.oas.annotations.media.Schema;
/**
* repository
*
*
* @author TopIAM
* Created by support@topiam.cn on 2021/9/11 22:32
* Created by support@topiam.cn on 2023/10/04 23:16
*/
@Repository
public interface AuditElasticSearchRepository extends
ElasticsearchRepository<AuditElasticSearchEntity, String> {
@Data
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "审计统计")
public class AuditStatisticsResult implements Serializable {
/**
* key
*/
private String key;
/**
* count
*/
private Long count;
}

View File

@ -1,5 +1,5 @@
/*
* eiam-console - Employee Identity and Access Management
* eiam-audit - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,12 +15,13 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console.pojo.result.analysis;
package cn.topiam.employee.audit.repository.result;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import io.swagger.v3.oas.annotations.media.Schema;
@ -31,6 +32,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
* Created by support@topiam.cn on 2020/11/22 23:16
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "认证量统计响应")
public class AuthnQuantityResult implements Serializable {

View File

@ -1,5 +1,5 @@
/*
* eiam-console - Employee Identity and Access Management
* eiam-audit - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,7 +15,9 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console.pojo.result.analysis;
package cn.topiam.employee.audit.repository.result;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
@ -33,7 +35,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "登录区域响应")
public class AuthnZoneResult {
public class AuthnZoneResult implements Serializable {
/**
* code

View File

@ -38,9 +38,7 @@ import com.google.common.collect.Lists;
import cn.topiam.employee.audit.controller.pojo.AuditListQuery;
import cn.topiam.employee.audit.controller.pojo.AuditListResult;
import cn.topiam.employee.audit.entity.Actor;
import cn.topiam.employee.audit.entity.AuditElasticSearchEntity;
import cn.topiam.employee.audit.entity.Event;
import cn.topiam.employee.audit.entity.AuditEntity;
import cn.topiam.employee.audit.entity.Target;
import cn.topiam.employee.audit.enums.TargetType;
import cn.topiam.employee.common.entity.account.OrganizationEntity;
@ -93,27 +91,25 @@ public interface AuditDataConverter {
* @param page {@link PageModel}
* @return {@link Page}
*/
default Page<AuditListResult> searchHitsConvertToAuditListResult(SearchHits<AuditElasticSearchEntity> search,
default Page<AuditListResult> searchHitsConvertToAuditListResult(SearchHits<AuditEntity> search,
PageModel page) {
List<AuditListResult> list = new ArrayList<>();
//总记录数
search.forEach(hit -> {
AuditElasticSearchEntity content = hit.getContent();
Event event = content.getEvent();
AuditEntity content = hit.getContent();
AuditListResult result = new AuditListResult();
result.setId(content.getId());
result.setEventStatus(event.getStatus());
result.setEventType(event.getType().getDesc());
result.setEventTime(event.getTime());
result.setId(content.getId().toString());
result.setEventStatus(content.getEventStatus());
result.setEventType(content.getEventType().getDesc());
result.setEventTime(content.getEventTime());
//用户代理
result.setUserAgent(content.getUserAgent());
result.setGeoLocation(content.getGeoLocation());
Actor actor = content.getActor();
//用户ID
result.setUserId(actor.getId());
result.setUsername(getUsername(actor.getType(), actor.getId()));
result.setUserId(content.getActorId());
result.setUsername(getUsername(content.getActorType(), content.getActorId()));
//用户类型
result.setUserType(actor.getType().getType());
result.setUserType(content.getActorType().getType());
//操作对象
if (Objects.nonNull(content.getTargets())) {
for (Target target : content.getTargets()) {

View File

@ -33,7 +33,7 @@ import org.springframework.util.CollectionUtils;
import cn.topiam.employee.audit.controller.pojo.AuditListQuery;
import cn.topiam.employee.audit.controller.pojo.AuditListResult;
import cn.topiam.employee.audit.controller.pojo.DictResult;
import cn.topiam.employee.audit.entity.AuditElasticSearchEntity;
import cn.topiam.employee.audit.entity.AuditEntity;
import cn.topiam.employee.audit.event.type.EventType;
import cn.topiam.employee.audit.service.AuditService;
import cn.topiam.employee.audit.service.converter.AuditDataConverter;
@ -71,8 +71,8 @@ public class AuditServiceImpl implements AuditService {
//查询入参转查询条件
NativeQuery nsq = auditDataConverter.auditListRequestConvertToNativeQuery(query, page);
//查询列表
SearchHits<AuditElasticSearchEntity> search = elasticsearchTemplate.search(nsq,
AuditElasticSearchEntity.class, IndexCoordinates
SearchHits<AuditEntity> search = elasticsearchTemplate.search(nsq, AuditEntity.class,
IndexCoordinates
.of(getAuditIndexPrefix(supportProperties.getAudit().getIndexPrefix()) + "*"));
//结果转返回结果
return auditDataConverter.searchHitsConvertToAuditListResult(search, page);

View File

@ -24,10 +24,8 @@ package cn.topiam.employee.common.constant;
* Created by support@topiam.cn on 2020/7/26 21:07
*/
public final class SecurityConstants {
/**
* CODE
*
*/
public static final String PASSWORD_POLICY_CODE = "default";
public static String DEFAULT_ADMIN_USERNAME = "admin";
}

View File

@ -1,5 +1,5 @@
/*
* eiam-synchronizer - Employee Identity and Access Management
* eiam-common - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.synchronizer.constants;
package cn.topiam.employee.common.constant;
import cn.topiam.employee.support.constant.EiamConstants;

View File

@ -55,13 +55,19 @@ public class OrganizationMemberEntity extends LogicDeleteEntity<Long> {
* ID
*/
@Column(name = "org_id")
private String orgId;
private String orgId;
/**
* ID
*/
@Column(name = "user_id")
private Long userId;
private Long userId;
/**
*
*/
@Column(name = "primary_")
private Boolean primary;
public OrganizationMemberEntity() {
}

View File

@ -1,227 +0,0 @@
/*
* eiam-common - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.common.entity.account;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.Setting;
import cn.topiam.employee.common.entity.account.po.OrganizationPO;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.persistence.Column;
import static org.springframework.data.elasticsearch.annotations.DateFormat.date;
import static org.springframework.data.elasticsearch.annotations.DateFormat.date_hour_minute_second_millis;
/**
* elasticsearch
*
* @author TopIAM
* Created by support@topiam.cn on 2023/4/12 23:22
*/
@Data
@Builder
@EqualsAndHashCode
@Document(indexName = "#{@userIndexName.getIndexName()}")
@Setting(replicas = 0)
public class UserElasticSearchEntity implements Serializable {
/**
* userId
*/
@Id
@Field(type = FieldType.Keyword, name = "id")
private String id;
/**
*
*/
@Field(type = FieldType.Keyword, name = "username")
private String username;
/**
*
*/
@Field(type = FieldType.Keyword, name = "email")
private String email;
/**
*
*/
@Field(type = FieldType.Keyword, name = "phone")
private String phone;
/**
*
*/
@Field(type = FieldType.Keyword, name = "phoneAreaCode")
private String phoneAreaCode;
/**
*
*/
@Field(type = FieldType.Keyword, name = "fullName")
private String fullName;
/**
*
*/
@Field(type = FieldType.Keyword, name = "nickName")
private String nickName;
/**
* URL
*/
@Field(type = FieldType.Keyword, name = "avatar")
private String avatar;
/**
* ENABLE: DISABLE: LOCKING:
*/
@Field(type = FieldType.Keyword, name = "status")
private String status;
/**
*
*/
@Field(type = FieldType.Keyword, name = "dataOrigin")
private String dataOrigin;
/**
* ID
*/
@Field(type = FieldType.Keyword, name = "identitySourceId")
private Long identitySourceId;
/**
*
*/
@Field(type = FieldType.Keyword, name = "emailVerified")
private Boolean emailVerified;
/**
*
*/
@Field(type = FieldType.Keyword, name = "phoneVerified")
private Boolean phoneVerified;
/**
*
*/
@Field(type = FieldType.Keyword, name = "authTotal")
private Long authTotal;
/**
* IP
*/
@Field(type = FieldType.Keyword, name = "lastAuthIp")
private String lastAuthIp;
/**
*
*/
@Field(type = FieldType.Date, name = "lastAuthTime", format = date_hour_minute_second_millis)
private LocalDateTime lastAuthTime;
/**
*
*/
@Column(name = "expand_")
private String expand;
/**
* ID
*/
@Column(name = "external_id")
private String externalId;
/**
*
*/
@Field(type = FieldType.Date, name = "expireDate", format = date)
private LocalDate expireDate;
/**
*
*/
@Field(type = FieldType.Date, name = "lastUpdatePasswordTime", format = date_hour_minute_second_millis)
private LocalDateTime lastUpdatePasswordTime;
// ----------------------------------用户详情----------------------------------
/**
*
*/
@Field(type = FieldType.Keyword, name = "idType")
private String idType;
/**
*
*/
@Field(type = FieldType.Keyword, name = "idCard")
private String idCard;
/**
*
*/
@Field(type = FieldType.Text, name = "website")
private String website;
/**u
*
*/
@Field(type = FieldType.Keyword, name = "address")
private String address;
/**
*
*/
@Field(type = FieldType.Object, name = "organizations")
private List<OrganizationPO> organizations;
/**
*
*/
@Field(type = FieldType.Object, name = "userGroups")
private List<UserGroup> userGroups;
@Data
@AllArgsConstructor
public static class UserGroup {
/**
* id
*/
@Field(type = FieldType.Keyword, name = "id")
private String id;
/**
*
*/
@Field(type = FieldType.Keyword, name = "name")
private String name;
}
}

View File

@ -30,7 +30,7 @@ import lombok.EqualsAndHashCode;
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class UserIdpBindPo extends UserIdpBindEntity {
public class UserIdpBindPO extends UserIdpBindEntity {
/**
*

View File

@ -41,4 +41,9 @@ public class UserPO extends UserEntity {
*
*/
private String orgDisplayPath;
/**
*
*/
private String primaryOrgDisplayPath;
}

View File

@ -1,35 +0,0 @@
/*
* eiam-common - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.common.repository.account;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
import cn.topiam.employee.common.entity.account.UserElasticSearchEntity;
/**
* repository
*
* @author TopIAM
* Created by support@topiam.cn on 2021/9/11 22:32
*/
@Repository
public interface UserElasticSearchRepository extends
ElasticsearchRepository<UserElasticSearchEntity, String> {
}

View File

@ -19,7 +19,7 @@ package cn.topiam.employee.common.repository.account;
import java.util.Optional;
import cn.topiam.employee.common.entity.account.po.UserIdpBindPo;
import cn.topiam.employee.common.entity.account.po.UserIdpBindPO;
import cn.topiam.employee.support.repository.page.domain.Page;
/**
@ -37,7 +37,7 @@ public interface UserIdpRepositoryCustomized {
* @param openId {@link String}
* @return {@link Optional}
*/
Optional<UserIdpBindPo> findByIdpIdAndOpenId(String idpId, String openId);
Optional<UserIdpBindPO> findByIdpIdAndOpenId(String idpId, String openId);
/**
* IDuserId
@ -46,7 +46,7 @@ public interface UserIdpRepositoryCustomized {
* @param userId {@link String}
* @return {@link Optional}
*/
Optional<UserIdpBindPo> findByIdpIdAndUserId(String idpId, Long userId);
Optional<UserIdpBindPO> findByIdpIdAndUserId(String idpId, Long userId);
/**
*
@ -54,5 +54,5 @@ public interface UserIdpRepositoryCustomized {
* @param userId {@link Long}
* @return {@link Page}
*/
Iterable<UserIdpBindPo> getUserIdpBindList(Long userId);
Iterable<UserIdpBindPO> getUserIdpBindList(Long userId);
}

View File

@ -21,17 +21,12 @@ import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import cn.topiam.employee.common.entity.account.UserElasticSearchEntity;
import cn.topiam.employee.common.entity.account.UserEntity;
import cn.topiam.employee.common.entity.account.po.UserEsPO;
import cn.topiam.employee.common.entity.account.po.UserPO;
import cn.topiam.employee.common.entity.account.query.UserListNotInGroupQuery;
import cn.topiam.employee.common.entity.account.query.UserListQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
/**
* User Repository Customized
*
@ -107,29 +102,4 @@ public interface UserRepositoryCustomized {
*/
void batchUpdate(List<UserEntity> list);
/**
*
*
* @param idList {@link List}
* @return {@link List}
*/
List<UserEsPO> getUserList(List<String> idList);
/**
* es
*
* @param userIndex {@link IndexCoordinates}
* @return {@link List}
*/
List<UserElasticSearchEntity> getAllUserElasticSearchEntity(IndexCoordinates userIndex);
/**
* es
*
* @param userIndex {@link IndexCoordinates}
* @param queryBuilder {@link Query}
* @return {@link List}
*/
List<UserElasticSearchEntity> getAllUserElasticSearchEntity(IndexCoordinates userIndex,
Query queryBuilder);
}

View File

@ -79,7 +79,8 @@ public class UserGroupMemberRepositoryCustomizedImpl implements
`u`.update_by,
`u`.update_time,
`u`.remark_,
group_concat( organization_.display_path ) AS org_display_path
group_concat( IF(organization_member.primary_ = 1, null, organization_.display_path ) ) AS primary_org_display_path,
group_concat( IF(organization_member.primary_ IS NULL, null, organization_.display_path ) ) AS org_display_path
FROM
user_group_member ugm
INNER JOIN user u ON ugm.user_id = u.id_ AND u.is_deleted = '0'

View File

@ -26,7 +26,7 @@ import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import cn.topiam.employee.common.entity.account.po.UserIdpBindPo;
import cn.topiam.employee.common.entity.account.po.UserIdpBindPO;
import cn.topiam.employee.common.repository.account.UserIdpRepositoryCustomized;
import cn.topiam.employee.common.repository.account.impl.mapper.UserIdpBindPoMapper;
import cn.topiam.employee.support.repository.page.domain.Page;
@ -53,7 +53,7 @@ public class UserIdpRepositoryCustomizedImpl implements UserIdpRepositoryCustomi
* @return {@link Optional}
*/
@Override
public Optional<UserIdpBindPo> findByIdpIdAndOpenId(String idpId, String openId) {
public Optional<UserIdpBindPO> findByIdpIdAndOpenId(String idpId, String openId) {
//@formatter:off
StringBuilder builder = new StringBuilder("SELECT uidp.*,`user`.username_,idp.name_ as idp_name FROM user_idp_bind uidp LEFT JOIN `user` ON uidp.user_id = `user`.id_ AND `user`.is_deleted = '0' LEFT JOIN identity_provider idp ON uidp.idp_id = idp.id_ AND idp.is_deleted = '0' WHERE uidp.is_deleted = '0' ");
//身份提供商ID
@ -67,7 +67,7 @@ public class UserIdpRepositoryCustomizedImpl implements UserIdpRepositoryCustomi
//@formatter:on
String sql = builder.toString();
try {
UserIdpBindPo userIdpBindPo = jdbcTemplate.queryForObject(sql,
UserIdpBindPO userIdpBindPo = jdbcTemplate.queryForObject(sql,
new UserIdpBindPoMapper());
return Optional.ofNullable(userIdpBindPo);
} catch (EmptyResultDataAccessException e) {
@ -83,7 +83,7 @@ public class UserIdpRepositoryCustomizedImpl implements UserIdpRepositoryCustomi
* @return {@link Optional}
*/
@Override
public Optional<UserIdpBindPo> findByIdpIdAndUserId(String idpId, Long userId) {
public Optional<UserIdpBindPO> findByIdpIdAndUserId(String idpId, Long userId) {
//@formatter:off
StringBuilder builder = new StringBuilder("SELECT uidp.*,`user`.username_,idp.name_ as idp_name FROM user_idp_bind uidp LEFT JOIN `user` ON uidp.user_id = `user`.id_ AND `user`.is_deleted = '0' LEFT JOIN identity_provider idp ON uidp.idp_id = idp.id_ AND idp.is_deleted = '0' WHERE uidp.is_deleted = '0' ");
//身份提供商ID
@ -97,7 +97,7 @@ public class UserIdpRepositoryCustomizedImpl implements UserIdpRepositoryCustomi
//@formatter:on
String sql = builder.toString();
try {
UserIdpBindPo userIdpBindPo = jdbcTemplate.queryForObject(sql,
UserIdpBindPO userIdpBindPo = jdbcTemplate.queryForObject(sql,
new UserIdpBindPoMapper());
return Optional.ofNullable(userIdpBindPo);
} catch (EmptyResultDataAccessException e) {
@ -112,7 +112,7 @@ public class UserIdpRepositoryCustomizedImpl implements UserIdpRepositoryCustomi
* @return {@link Page}
*/
@Override
public Iterable<UserIdpBindPo> getUserIdpBindList(Long userId) {
public Iterable<UserIdpBindPO> getUserIdpBindList(Long userId) {
//@formatter:off
StringBuilder builder = new StringBuilder("SELECT uidp.*,idp.name_ as idp_name FROM user_idp_bind uidp LEFT JOIN identity_provider idp ON uidp.idp_id = idp.id_ AND idp.is_deleted = '0' WHERE uidp.is_deleted = '0' ");
//用户ID

View File

@ -21,7 +21,6 @@ import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@ -31,34 +30,21 @@ import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.client.elc.NativeQueryBuilder;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHitsIterator;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.util.CollectionUtils;
import cn.topiam.employee.common.entity.account.UserElasticSearchEntity;
import cn.topiam.employee.common.entity.account.UserEntity;
import cn.topiam.employee.common.entity.account.po.UserEsPO;
import cn.topiam.employee.common.entity.account.po.UserPO;
import cn.topiam.employee.common.entity.account.query.UserListNotInGroupQuery;
import cn.topiam.employee.common.entity.account.query.UserListQuery;
import cn.topiam.employee.common.repository.account.UserRepositoryCustomized;
import cn.topiam.employee.common.repository.account.impl.mapper.UserEntityMapper;
import cn.topiam.employee.common.repository.account.impl.mapper.UserEsMapper;
import cn.topiam.employee.common.repository.account.impl.mapper.UserPoMapper;
import lombok.AllArgsConstructor;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders;
import static cn.topiam.employee.common.constant.AccountConstants.USER_CACHE_NAME;
/**
@ -83,7 +69,7 @@ public class UserRepositoryCustomizedImpl implements UserRepositoryCustomized {
@Override
public Page<UserPO> getUserList(UserListQuery query, Pageable pageable) {
//@formatter:off
StringBuilder builder = new StringBuilder("SELECT `user`.id_, `user`.username_,`user`.password_, `user`.email_, `user`.phone_,`user`.phone_area_code, `user`.full_name ,`user`.nick_name, `user`.avatar_ , `user`.status_, `user`.data_origin, `user`.email_verified, `user`.phone_verified, `user`.auth_total, `user`.last_auth_ip, `user`.last_auth_time, `user`.expand_, `user`.external_id , `user`.expire_date,`user`.create_by, `user`.create_time, `user`.update_by , `user`.update_time, `user`.remark_, group_concat(organization_.display_path) AS org_display_path FROM `user` INNER JOIN `organization_member` ON (`user`.id_ = organization_member.user_id) INNER JOIN `organization` organization_ ON (organization_.id_ = organization_member.org_id) WHERE `user`.is_deleted = 0");
StringBuilder builder = new StringBuilder("SELECT `user`.id_, `user`.username_,`user`.password_, `user`.email_, `user`.phone_,`user`.phone_area_code, `user`.full_name ,`user`.nick_name, `user`.avatar_ , `user`.status_, `user`.data_origin, `user`.email_verified, `user`.phone_verified, `user`.auth_total, `user`.last_auth_ip, `user`.last_auth_time, `user`.expand_, `user`.external_id , `user`.expire_date,`user`.create_by, `user`.create_time, `user`.update_by , `user`.update_time, `user`.remark_, group_concat( IF(organization_member.primary_ = 1, null, organization_.display_path ) ) AS primary_org_display_path, group_concat( IF(organization_member.primary_ IS NULL, null, organization_.display_path ) ) AS org_display_path FROM `user` INNER JOIN `organization_member` ON (`user`.id_ = organization_member.user_id) INNER JOIN `organization` organization_ ON (organization_.id_ = organization_member.org_id) WHERE `user`.is_deleted = 0");
//组织条件
if (StringUtils.isNotBlank(query.getOrganizationId())) {
if (Boolean.TRUE.equals(query.getInclSubOrganization())) {
@ -169,7 +155,8 @@ public class UserRepositoryCustomizedImpl implements UserRepositoryCustomized {
`user`.update_by,
`user`.update_time,
`user`.remark_,
group_concat( organization_.display_path ) AS org_display_path
group_concat( IF(organization_member.primary_ = 1, null, organization_.display_path ) ) AS primary_org_display_path,
group_concat( IF(organization_member.primary_ IS NULL, null, organization_.display_path ) ) AS org_display_path
FROM `user`
LEFT JOIN `organization_member` ON ( `user`.id_ = organization_member.user_id AND organization_member.is_deleted = '0' )
LEFT JOIN `organization` organization_ ON ( organization_.id_ = organization_member.org_id AND organization_.is_deleted = '0' )
@ -362,120 +349,6 @@ public class UserRepositoryCustomizedImpl implements UserRepositoryCustomized {
//@formatter:on
}
@Override
public List<UserEsPO> getUserList(List<String> idList) {
//@formatter:off
String whereUserId = "";
if (!CollectionUtils.isEmpty(idList)) {
if (idList.size() > 1) {
whereUserId = "AND `user`.id_ IN ('%s')".formatted(String.join("','", idList));
}
else {
whereUserId = "AND `user`.id_ = '%s'".formatted(idList.get(0));
}
}
String sql = """
SELECT
`user`.id_,
`user`.username_,
`user`.email_,
`user`.phone_,
`user`.phone_area_code,
`user`.full_name,
`user`.nick_name,
`user`.avatar_,
`user`.status_,
`user`.data_origin,
`user`.email_verified,
`user`.phone_verified,
`user`.auth_total,
`user`.last_auth_ip,
`user`.last_auth_time,
`user`.expand_,
`user`.external_id,
`user`.expire_date,
`user`.create_by,
`user`.create_time,
`user`.update_by,
`user`.update_time,
`user`.remark_,
`user`.identity_source_id,
`user`.last_update_password_time,
`user_detail`.id_type,
`user_detail`.id_card,
`user_detail`.website_,
`user_detail`.address_,
GROUP_CONCAT( DISTINCT `organization_member`.org_id SEPARATOR ',' ) AS organization_ids,
CONCAT( '{', GROUP_CONCAT( DISTINCT CONCAT( '"', user_group.id_, '": "', user_group.name_, '"' ) SEPARATOR ',' ), '}' ) AS user_groups
FROM
`user`
LEFT JOIN `user_detail` ON `user`.id_ = user_detail.user_id
AND user_detail.is_deleted = '0'
LEFT JOIN `organization_member` ON `user`.id_ = organization_member.user_id
AND organization_member.is_deleted = '0'
LEFT JOIN `user_group_member` ON `user`.id_ = user_group_member.user_id
AND user_group_member.is_deleted = '0'
LEFT JOIN `user_group` ON `user_group`.id_ = user_group_member.group_id
AND user_group.is_deleted = '0'
WHERE
`user`.is_deleted = 0 %s
GROUP BY
`user`.id_,
`user`.username_,
`user`.email_,
`user`.phone_,
`user`.phone_area_code,
`user`.full_name,
`user`.nick_name,
`user`.avatar_,
`user`.status_,
`user`.data_origin,
`user`.email_verified,
`user`.phone_verified,
`user`.auth_total,
`user`.last_auth_ip,
`user`.last_auth_time,
`user`.expand_,
`user`.external_id,
`user`.expire_date,
`user`.create_by,
`user`.create_time,
`user`.update_by,
`user`.update_time,
`user`.remark_,
`user`.identity_source_id,
`user`.last_update_password_time
""".formatted(whereUserId);
//@formatter:on
return jdbcTemplate.query(sql, new UserEsMapper());
}
@Override
public List<UserElasticSearchEntity> getAllUserElasticSearchEntity(IndexCoordinates userIndex) {
return getAllUserElasticSearchEntity(userIndex,
QueryBuilders.matchAll().build()._toQuery());
}
@Override
public List<UserElasticSearchEntity> getAllUserElasticSearchEntity(IndexCoordinates userIndex,
Query query) {
if (!elasticsearchTemplate.indexOps(userIndex).exists()) {
return null;
}
NativeQuery searchQuery = new NativeQueryBuilder().withQuery(query)
// 设置每页数据量
.withPageable(PageRequest.of(0, 2000)).build();
List<UserElasticSearchEntity> userElasticSearchEntityList = new ArrayList<>();
SearchHitsIterator<UserElasticSearchEntity> searchScrollHits = elasticsearchTemplate
.searchForStream(searchQuery, UserElasticSearchEntity.class, userIndex);
while (searchScrollHits.hasNext()) {
SearchHit<UserElasticSearchEntity> next = searchScrollHits.next();
userElasticSearchEntityList.add(next.getContent());
}
return userElasticSearchEntityList;
}
/**
* JdbcTemplate
*/

View File

@ -1,111 +0,0 @@
/*
* eiam-common - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.common.repository.account.impl.mapper;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.lang.NonNull;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import cn.topiam.employee.common.entity.account.po.UserEsPO;
import cn.topiam.employee.common.enums.DataOrigin;
import cn.topiam.employee.common.enums.UserStatus;
import lombok.extern.slf4j.Slf4j;
/**
* @author TopIAM
* Created by support@topiam.cn on 2023/5/30 22:25
*/
@Slf4j
public class UserEsMapper implements RowMapper<UserEsPO> {
/**
* Implementations must implement this method to map each row of data
* in the ResultSet. This method should not call {@code next()} on
* the ResultSet; it is only supposed to map values of the current row.
*
* @param rs the ResultSet to map (pre-initialized for the current row)
* @param rowNum the number of the current row
* @return the result object for the current row (may be {@code null})
* @throws SQLException if an SQLException is encountered getting
* column values (that is, there's no need to catch SQLException)
*/
@Override
public UserEsPO mapRow(@NonNull ResultSet rs, int rowNum) throws SQLException {
//@formatter:off
UserEsPO user = new UserEsPO();
user.setId(rs.getLong("id_"));
user.setUsername(rs.getString("username_"));
user.setEmail(rs.getString("email_"));
user.setPhone(rs.getString("phone_"));
user.setPhoneAreaCode(rs.getString("phone_area_code"));
user.setFullName(rs.getString("full_name"));
user.setNickName(rs.getString("nick_name"));
user.setAvatar(rs.getString("avatar_"));
user.setStatus(UserStatus.getStatus(rs.getString("status_")));
user.setDataOrigin(DataOrigin.getType(rs.getString("data_origin")));
user.setEmailVerified(rs.getBoolean("email_verified"));
user.setAuthTotal(rs.getLong("auth_total"));
user.setLastAuthIp(rs.getString("last_auth_ip"));
user.setLastAuthTime(ObjectUtils.isNotEmpty(rs.getTimestamp("last_auth_time")) ? rs.getTimestamp("last_auth_time").toLocalDateTime() : null);
user.setExpand(rs.getString("expand_"));
user.setExternalId(rs.getString("external_id"));
user.setIdentitySourceId(rs.getLong("identity_source_id"));
user.setExpireDate(ObjectUtils.isNotEmpty(rs.getTimestamp("expire_date")) ? rs.getDate("expire_date").toLocalDate() : null);
user.setLastAuthTime(ObjectUtils.isNotEmpty(rs.getTimestamp("last_auth_time")) ? rs.getTimestamp("last_auth_time").toLocalDateTime() : null);
user.setCreateBy(rs.getString("create_by"));
user.setCreateTime(ObjectUtils.isNotEmpty(rs.getTimestamp("create_time")) ? rs.getTimestamp("create_time").toLocalDateTime() : null);
user.setUpdateBy(rs.getString("update_by"));
user.setUpdateTime(ObjectUtils.isNotEmpty(rs.getTimestamp("update_time")) ? rs.getTimestamp("update_time").toLocalDateTime() : null);
user.setRemark(rs.getString("remark_"));
user.setLastUpdatePasswordTime(ObjectUtils.isNotEmpty(rs.getTimestamp("last_update_password_time")) ? rs.getTimestamp("last_update_password_time").toLocalDateTime() : null);
// 额外数据
user.setIdType(rs.getString("id_type"));
user.setIdCard(rs.getString("id_card"));
user.setWebsite(rs.getString("website_"));
user.setAddress(rs.getString("address_"));
String organizationIds = rs.getString("organization_ids");
if (StringUtils.isNotBlank(organizationIds)) {
user.setOrganizationIds(Set.of((organizationIds.split(","))));
}
ObjectMapper objectMapper = new ObjectMapper();
String userGroups = rs.getString("user_groups");
if (StringUtils.isNotBlank(userGroups)) {
Map<String, String> userGroupMap;
try {
userGroupMap = objectMapper.readValue(userGroups, new TypeReference<>() {
});
} catch (JsonProcessingException e) {
log.error("解析用户组字段json异常:[{}]", e.getMessage());
throw new RuntimeException(e);
}
user.setUserGroups(userGroupMap);
}
return user;
//@formatter:on
}
}

View File

@ -24,14 +24,14 @@ import org.apache.commons.lang3.ObjectUtils;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.lang.NonNull;
import cn.topiam.employee.common.entity.account.po.UserIdpBindPo;
import cn.topiam.employee.common.entity.account.po.UserIdpBindPO;
/**
* @author TopIAM
* Created by support@topiam.cn on 2022/2/13 22:25
*/
@SuppressWarnings("DuplicatedCode")
public class UserIdpBindPoMapper implements RowMapper<UserIdpBindPo> {
public class UserIdpBindPoMapper implements RowMapper<UserIdpBindPO> {
/**
* Implementations must implement this method to map each row of data
* in the ResultSet. This method should not call {@code next()} on
@ -44,9 +44,9 @@ public class UserIdpBindPoMapper implements RowMapper<UserIdpBindPo> {
* column values (that is, there's no need to catch SQLException)
*/
@Override
public UserIdpBindPo mapRow(@NonNull ResultSet rs, int rowNum) throws SQLException {
public UserIdpBindPO mapRow(@NonNull ResultSet rs, int rowNum) throws SQLException {
//@formatter:off
UserIdpBindPo userIdpBindPo = new UserIdpBindPo();
UserIdpBindPO userIdpBindPo = new UserIdpBindPO();
userIdpBindPo.setId(rs.getLong("id_"));
userIdpBindPo.setUserId(rs.getLong("user_id"));
userIdpBindPo.setOpenId(rs.getString("open_id"));

View File

@ -70,6 +70,7 @@ public class UserPoMapper implements RowMapper<UserPO> {
user.setExpireDate(ObjectUtils.isNotEmpty(rs.getTimestamp("expire_date")) ? rs.getDate("expire_date").toLocalDate() : null);
user.setLastAuthTime(ObjectUtils.isNotEmpty(rs.getTimestamp("last_auth_time")) ? rs.getTimestamp("last_auth_time").toLocalDateTime() : null);
user.setOrgDisplayPath(rs.getString("org_display_path"));
user.setPrimaryOrgDisplayPath(rs.getString("primary_org_display_path"));
//额外数据
user.setCreateBy(rs.getString("create_by"));
user.setCreateTime(ObjectUtils.isNotEmpty(rs.getTimestamp("create_time")) ? rs.getTimestamp("create_time").toLocalDateTime() : null);

View File

@ -63,7 +63,7 @@ public interface AdministratorRepository extends LogicDeleteRepository<Administr
*/
@NotNull
@Cacheable
@Query(value = "SELECT * FROM administrator WHERE id_ = :id", nativeQuery = true)
@Query(value = "SELECT AdministratorEntity FROM AdministratorEntity WHERE id = :id")
Optional<AdministratorEntity> findByIdContainsDeleted(@NotNull @Param(value = "id") Long id);
/**
@ -89,7 +89,6 @@ public interface AdministratorRepository extends LogicDeleteRepository<Administr
*
* @param entity must not be {@literal null}.
* @return {@link AdministratorEntity}
* @param <S>
*/
@Override
@CacheEvict(allEntries = true)
@ -128,7 +127,7 @@ public interface AdministratorRepository extends LogicDeleteRepository<Administr
@Transactional(rollbackFor = Exception.class)
@Modifying
@CacheEvict(allEntries = true)
@Query(value = "update administrator set status_ = ?2 where id_ = ?1", nativeQuery = true)
@Query(value = "update AdministratorEntity set status = :status where id = :id")
void updateStatus(@Param(value = "id") String id, @Param(value = "status") String status);
/**
@ -140,7 +139,7 @@ public interface AdministratorRepository extends LogicDeleteRepository<Administr
@Transactional(rollbackFor = Exception.class)
@Modifying
@CacheEvict(allEntries = true)
@Query(value = "update administrator set password_ = ?2 where id_ = ?1", nativeQuery = true)
@Query(value = "update AdministratorEntity set password = :password where id = :id")
void updatePassword(@Param(value = "id") String id, @Param(value = "password") String password);
/**

View File

@ -82,11 +82,22 @@
<constraints nullable="true"/>
</column>
</createTable>
<!-- 添加字段 -->
<addColumn tableName="organization_member">
<column name="primary_" remarks="主组织" type="TINYINT(1)">
<constraints nullable="true"/>
</column>
</addColumn>
<!--创建索引-->
<createIndex tableName="app_group_association" indexName="uk_app_group_association" unique="true">
<column name="group_id"/>
<column name="app_id"/>
<column name="is_deleted"/>
</createIndex>
<createIndex tableName="organization_member" indexName="uk_organization_member_primary" unique="true">
<column name="user_id"/>
<column name="primary_"/>
<column name="is_deleted"/>
</createIndex>
</changeSet>
</databaseChangeLog>

View File

@ -148,7 +148,7 @@
<phase>generate-resources</phase>
<configuration>
<nodeVersion>v18.17.1</nodeVersion>
<pnpmVersion>v8.7.0</pnpmVersion>
<pnpmVersion>v8.8.0</pnpmVersion>
<nodeDownloadRoot>https://npm.taobao.org/mirrors/node/</nodeDownloadRoot>
</configuration>
</execution>
@ -201,7 +201,7 @@
<phase>generate-resources</phase>
<configuration>
<nodeVersion>v18.17.1</nodeVersion>
<pnpmVersion>v8.7.0</pnpmVersion>
<pnpmVersion>v8.8.0</pnpmVersion>
<nodeDownloadRoot>https://npm.taobao.org/mirrors/node/</nodeDownloadRoot>
</configuration>
</execution>

View File

@ -75,7 +75,7 @@
"rc-select": "^14.9.0",
"rc-tree": "^5.7.12",
"react": "^18.2.0",
"react-codemirror2": "^7.2.1",
"react-codemirror2": "^7.3.0",
"react-dom": "^18.2.0",
"react-fast-marquee": "^1.6.1",
"react-helmet-async": "^1.3.0",
@ -92,8 +92,8 @@
"@types/lodash": "^4.14.199",
"@types/numeral": "^2.0.3",
"@types/qs": "^6.9.8",
"@types/react": "^18.2.23",
"@types/react-dom": "^18.2.8",
"@types/react": "^18.2.25",
"@types/react-dom": "^18.2.10",
"@types/react-helmet": "^6.1.7",
"@umijs/lint": "^4.0.83",
"@umijs/max": "^4.0.83",

View File

@ -39,6 +39,8 @@ import {
Table,
Tooltip,
Typography,
Tag,
Popover,
} from 'antd';
import React, { useRef, useState } from 'react';
import CreateUser from '../CreateUser';
@ -182,6 +184,44 @@ export default (props: UserListProps) => {
dataIndex: 'orgDisplayPath',
search: false,
ellipsis: true,
render: (_, record) => [
<Popover
key="pop"
title={
<Tag color={'geekblue'} key={record.orgDisplayPath}>
{record.primaryOrgDisplayPath}
</Tag>
}
content={
<Space direction="vertical" size="small" style={{ display: 'flex' }}>
{record.orgDisplayPath.split(',')?.map((p: string) => {
return (
<Tag color={'green'} key={p}>
{p}
</Tag>
);
})}
</Space>
}
>
<Space key="primary_path">
{
<Tag color={'geekblue'} key={record.primaryOrgDisplayPath}>
{record.primaryOrgDisplayPath}
</Tag>
}
</Space>
<Space key="path" direction="vertical" size="small" style={{ display: 'flex' }}>
{record.orgDisplayPath.split(',')?.map((p: string) => {
return (
<Tag color={'green'} key={p}>
{p}
</Tag>
);
})}
</Space>
</Popover>,
],
},
{
title: intl.formatMessage({ id: 'pages.account.user_list.user.columns.status' }),

View File

@ -273,7 +273,9 @@ export async function removeUser(id: string): Promise<API.ApiResult<boolean>> {
/**
* Remove Batch User
*/
export async function removeBatchUser(ids: (number | string)[]): Promise<API.ApiResult<boolean>> {
export async function removeBatchUser(
ids: (number | string | Key)[],
): Promise<API.ApiResult<boolean>> {
return request<API.ApiResult<boolean>>(`/api/v1/user/batch_delete`, {
method: 'DELETE',
params: { ids: ids },

View File

@ -28,7 +28,7 @@ import org.springframework.boot.web.servlet.ServletComponentScan;
* Created by support@topiam.cn on 2020/7/9
*/
@ServletComponentScan
@SpringBootApplication(scanBasePackages = { "cn.topiam.employee" })
@SpringBootApplication
public class EiamConsoleApplication {
public static void main(String[] args) {
SpringApplication.run(EiamConsoleApplication.class, args);

View File

@ -1,31 +0,0 @@
/*
* eiam-console - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console.access;
/**
*
* @author TopIAM
* Created by support@topiam.cn on 2023/5/24 23:16
*/
public interface DefaultAdministratorConstants {
/**
*
*/
String DEFAULT_ADMIN_USERNAME = "admin";
}

View File

@ -54,19 +54,14 @@ import cn.topiam.employee.audit.event.AuditEventPublish;
import cn.topiam.employee.authentication.common.jackjson.AuthenticationJacksonModule;
import cn.topiam.employee.common.constant.AuthorizeConstants;
import cn.topiam.employee.common.entity.setting.SettingEntity;
import cn.topiam.employee.common.repository.account.OrganizationRepository;
import cn.topiam.employee.common.repository.account.UserElasticSearchRepository;
import cn.topiam.employee.common.repository.account.UserRepository;
import cn.topiam.employee.common.repository.setting.AdministratorRepository;
import cn.topiam.employee.common.repository.setting.SettingRepository;
import cn.topiam.employee.console.handler.*;
import cn.topiam.employee.console.listener.ConsoleAuthenticationFailureEventListener;
import cn.topiam.employee.console.listener.ConsoleAuthenticationSuccessEventListener;
import cn.topiam.employee.console.listener.ConsoleLogoutSuccessEventListener;
import cn.topiam.employee.console.listener.ConsoleSessionInformationExpiredStrategy;
import cn.topiam.employee.core.dynamic.UserSyncTask;
import cn.topiam.employee.console.security.handler.*;
import cn.topiam.employee.console.security.listener.ConsoleAuthenticationFailureEventListener;
import cn.topiam.employee.console.security.listener.ConsoleAuthenticationSuccessEventListener;
import cn.topiam.employee.console.security.listener.ConsoleLogoutSuccessEventListener;
import cn.topiam.employee.console.security.listener.ConsoleSessionInformationExpiredStrategy;
import cn.topiam.employee.core.security.form.FormLoginSecretFilter;
import cn.topiam.employee.support.autoconfiguration.SupportProperties;
import cn.topiam.employee.support.geo.GeoLocationService;
import cn.topiam.employee.support.jackjson.SupportJackson2Module;
import cn.topiam.employee.support.security.authentication.WebAuthenticationDetailsSource;
@ -357,24 +352,6 @@ public class ConsoleSecurityConfiguration implements BeanClassLoaderAware {
return new GenericJackson2JsonRedisSerializer(mapper);
}
/**
* es
*
* @param supportProperties {@link SupportProperties}
* @param userElasticSearchRepository {@link UserElasticSearchRepository}
* @param userRepository {@link UserRepository}
* @param organizationRepository {@link OrganizationRepository}
* @return {@link UserSyncTask}
*/
@Bean
public UserSyncTask userSyncTask(SupportProperties supportProperties,
UserElasticSearchRepository userElasticSearchRepository,
UserRepository userRepository,
OrganizationRepository organizationRepository) {
return new UserSyncTask(supportProperties, userElasticSearchRepository, userRepository,
organizationRepository);
}
/**
* WebAuthenticationDetailsSource
*

View File

@ -25,6 +25,8 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.topiam.employee.audit.repository.result.AuthnQuantityResult;
import cn.topiam.employee.audit.repository.result.AuthnZoneResult;
import cn.topiam.employee.console.pojo.query.analysis.AnalysisQuery;
import cn.topiam.employee.console.pojo.result.analysis.*;
import cn.topiam.employee.console.service.analysis.AnalysisService;

View File

@ -22,7 +22,7 @@ import java.util.List;
import org.mapstruct.Mapper;
import cn.topiam.employee.common.entity.account.po.UserIdpBindPo;
import cn.topiam.employee.common.entity.account.po.UserIdpBindPO;
import cn.topiam.employee.console.pojo.result.app.UserIdpBindListResult;
import cn.topiam.employee.support.repository.page.domain.Page;
@ -41,9 +41,9 @@ public interface UserIdpBindConverter {
* @param page {@link Page}
* @return {@link Page}
*/
default List<UserIdpBindListResult> userIdpBindEntityConvertToUserIdpBindListResult(Iterable<UserIdpBindPo> page) {
default List<UserIdpBindListResult> userIdpBindEntityConvertToUserIdpBindListResult(Iterable<UserIdpBindPO> page) {
List<UserIdpBindListResult> list = new ArrayList<>();
for (UserIdpBindPo entity : page) {
for (UserIdpBindPO entity : page) {
list.add(entityConvertToAppAccountResult(entity));
}
return list;
@ -52,8 +52,8 @@ public interface UserIdpBindConverter {
/**
*
*
* @param userIdpBindPo {@link UserIdpBindPo}
* @param userIdpBindPo {@link UserIdpBindPO}
* @return {@link UserIdpBindListResult}
*/
UserIdpBindListResult entityConvertToAppAccountResult(UserIdpBindPo userIdpBindPo);
UserIdpBindListResult entityConvertToAppAccountResult(UserIdpBindPO userIdpBindPo);
}

View File

@ -100,7 +100,7 @@ public interface AdministratorConverter {
* @param page {@link AdministratorEntity}
* @return {@link AdministratorListResult}
*/
@Mapping(target = "initialized", expression = "java(page.getUsername().equals(cn.topiam.employee.console.access.DefaultAdministratorConstants.DEFAULT_ADMIN_USERNAME))")
@Mapping(target = "initialized", expression = "java(page.getUsername().equals(cn.topiam.employee.common.constant.SecurityConstants.DEFAULT_ADMIN_USERNAME))")
@Mapping(target = "status", source = "status.code")
@Mapping(target = "emailVerified", source = "emailVerified", defaultValue = "false")
@Mapping(target = "authTotal", source = "authTotal", defaultValue = "0L")
@ -195,7 +195,7 @@ public interface AdministratorConverter {
* @return {@link AdministratorResult}
*/
@Mapping(target = "status", source = "status.code")
@Mapping(target = "initialized", expression = "java(user.getUsername().equals(cn.topiam.employee.console.access.DefaultAdministratorConstants.DEFAULT_ADMIN_USERNAME))")
@Mapping(target = "initialized", expression = "java(user.getUsername().equals(cn.topiam.employee.common.constant.SecurityConstants.DEFAULT_ADMIN_USERNAME))")
AdministratorResult entityConvertToAdministratorDetailsResult(AdministratorEntity user);
/**

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console.initialize;
package cn.topiam.employee.console.initializer;
import java.io.BufferedWriter;
import java.io.File;
@ -42,7 +42,7 @@ import cn.topiam.employee.common.entity.setting.AdministratorEntity;
import cn.topiam.employee.common.enums.UserStatus;
import cn.topiam.employee.common.repository.setting.AdministratorRepository;
import cn.topiam.employee.support.trace.TraceUtils;
import static cn.topiam.employee.console.access.DefaultAdministratorConstants.DEFAULT_ADMIN_USERNAME;
import static cn.topiam.employee.common.constant.SecurityConstants.DEFAULT_ADMIN_USERNAME;
import static cn.topiam.employee.support.lock.LockAspect.getTopiamLockKeyPrefix;
import static cn.topiam.employee.support.util.CreateFileUtil.createFile;
@ -54,10 +54,10 @@ import static cn.topiam.employee.support.util.CreateFileUtil.createFile;
*/
@Order(2)
@Component
public class DefaultAdministratorInitialize implements ApplicationListener<ContextRefreshedEvent> {
public class DefaultAdministratorInitializer implements ApplicationListener<ContextRefreshedEvent> {
private final Logger logger = LoggerFactory
.getLogger(DefaultAdministratorInitialize.class);
.getLogger(DefaultAdministratorInitializer.class);
private static final String DIR_NAME = ".topiam";
private static final String USER_HOME = "user.home";
@ -158,9 +158,9 @@ public class DefaultAdministratorInitialize implements ApplicationListener<Conte
private final RedissonClient redissonClient;
public DefaultAdministratorInitialize(AdministratorRepository administratorRepository,
PasswordEncoder passwordEncoder,
RedissonClient redissonClient) {
public DefaultAdministratorInitializer(AdministratorRepository administratorRepository,
PasswordEncoder passwordEncoder,
RedissonClient redissonClient) {
this.administratorRepository = administratorRepository;
this.passwordEncoder = passwordEncoder;
this.redissonClient = redissonClient;

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console.initialize;
package cn.topiam.employee.console.initializer;
import java.util.Arrays;
import java.util.Optional;
@ -47,7 +47,7 @@ import static cn.topiam.employee.support.lock.LockAspect.getTopiamLockKeyPrefix;
*/
@Order(2)
@Component
public class DefaultAppGroupInitialize implements ApplicationListener<ContextRefreshedEvent> {
public class DefaultAppGroupInitializer implements ApplicationListener<ContextRefreshedEvent> {
@Override
@Transactional(rollbackFor = Exception.class)
@ -94,8 +94,8 @@ public class DefaultAppGroupInitialize implements ApplicationListener<ContextRef
private final RedissonClient redissonClient;
public DefaultAppGroupInitialize(AppGroupRepository appGroupRepository,
RedissonClient redissonClient) {
public DefaultAppGroupInitializer(AppGroupRepository appGroupRepository,
RedissonClient redissonClient) {
this.appGroupRepository = appGroupRepository;
this.redissonClient = redissonClient;
}

View File

@ -69,15 +69,15 @@ public class AnalysisQuery implements Serializable {
/**
* HOUR
*/
HOUR(CalendarInterval.Hour, "HH时"),
HOUR(CalendarInterval.Hour, "%h时"),
/**
* DAY
*/
DAY(CalendarInterval.Day, "dd日"),
DAY(CalendarInterval.Day, "%d日"),
/**
* MONTH
*/
MONTH(CalendarInterval.Month, "MM月");
MONTH(CalendarInterval.Month, "%m月");
private final CalendarInterval type;
private final String format;

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console.handler;
package cn.topiam.employee.console.security.handler;
import java.io.IOException;

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console.handler;
package cn.topiam.employee.console.security.handler;
import java.io.IOException;

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console.handler;
package cn.topiam.employee.console.security.handler;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console.handler;
package cn.topiam.employee.console.security.handler;
import java.util.List;

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console.handler;
package cn.topiam.employee.console.security.handler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -21,4 +21,4 @@
* @author TopIAM
* Created by support@topiam.cn on 2020/10/29 23:12
*/
package cn.topiam.employee.console.handler;
package cn.topiam.employee.console.security.handler;

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console.listener;
package cn.topiam.employee.console.security.listener;
import java.util.Optional;

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console.listener;
package cn.topiam.employee.console.security.listener;
import org.springframework.context.ApplicationListener;
import org.springframework.lang.NonNull;

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console.listener;
package cn.topiam.employee.console.security.listener;
import java.util.List;

View File

@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console.listener;
package cn.topiam.employee.console.security.listener;
import org.springframework.http.HttpStatus;
import org.springframework.security.web.session.SessionInformationExpiredEvent;

View File

@ -15,4 +15,4 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee;
package cn.topiam.employee.console.security.listener;

View File

@ -15,4 +15,4 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console;
package cn.topiam.employee.console.security;

View File

@ -21,7 +21,6 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
@ -34,18 +33,14 @@ import cn.topiam.employee.audit.context.AuditContext;
import cn.topiam.employee.audit.entity.Target;
import cn.topiam.employee.audit.enums.TargetType;
import cn.topiam.employee.common.entity.account.OrganizationEntity;
import cn.topiam.employee.common.entity.account.OrganizationMemberEntity;
import cn.topiam.employee.common.entity.account.QUserEntity;
import cn.topiam.employee.common.enums.DataOrigin;
import cn.topiam.employee.common.repository.account.OrganizationMemberRepository;
import cn.topiam.employee.common.repository.account.OrganizationRepository;
import cn.topiam.employee.console.converter.account.OrganizationConverter;
import cn.topiam.employee.console.pojo.result.account.*;
import cn.topiam.employee.console.pojo.save.account.OrganizationCreateParam;
import cn.topiam.employee.console.pojo.update.account.OrganizationUpdateParam;
import cn.topiam.employee.console.service.account.OrganizationService;
import cn.topiam.employee.core.mq.UserMessagePublisher;
import cn.topiam.employee.core.mq.UserMessageTag;
import cn.topiam.employee.support.repository.id.SnowflakeIdGenerator;
import cn.topiam.employee.support.util.BeanUtils;
@ -116,7 +111,6 @@ public class OrganizationServiceImpl implements OrganizationService {
Optional<OrganizationEntity> optional = this.organizationRepository.findById(param.getId());
if (optional.isPresent()) {
OrganizationEntity entity = optional.get();
String userIds;
//如果修改了名字,递归修改和该组织有关所有节点信息的展示路径
if (!optional.get().getName().equals(param.getName())) {
//修改名称
@ -126,22 +120,10 @@ public class OrganizationServiceImpl implements OrganizationService {
if (!entity.getLeaf()) {
recursiveUpdateDisplayPath(entity.getId(), entity.getId(), param.getName());
}
userIds = organizationRepository
.getOrgMemberList(organization.getId(), QUserEntity.userEntity.id).stream()
.map(String::valueOf).collect(Collectors.joining(","));
} else {
List<OrganizationMemberEntity> orgMemberList = organizationMemberRepository
.findAllByOrgId(entity.getId());
userIds = orgMemberList.stream().map(item -> String.valueOf(item.getUserId()))
.collect(Collectors.joining(","));
}
//修改
BeanUtils.merge(organization, entity, LAST_MODIFIED_BY, LAST_MODIFIED_TIME);
organizationRepository.save(entity);
// 更新用户es信息
if (StringUtils.isNotBlank(userIds)) {
userMessagePublisher.sendUserChangeMessage(UserMessageTag.SAVE, userIds);
}
AuditContext.setTarget(
Target.builder().id(entity.getId()).type(TargetType.ORGANIZATION).build());
return true;
@ -259,9 +241,7 @@ public class OrganizationServiceImpl implements OrganizationService {
@Override
public OrganizationResult getOrganization(String id) {
Optional<OrganizationEntity> entity = organizationRepository.findById(id);
OrganizationResult organizationResult = entity
.map(organizationConverter::entityConvertToGetOrganizationResult).orElse(null);
return organizationResult;
return entity.map(organizationConverter::entityConvertToGetOrganizationResult).orElse(null);
}
/**
@ -315,13 +295,6 @@ public class OrganizationServiceImpl implements OrganizationService {
.name(organization.get().getName()).build());
//存在子组织,递归更改子组织 path 和 displayPath
recursiveUpdateChildNodePathAndDisplayPath(entity.getId());
// 更新用户es信息
String userIds = organizationRepository
.getOrgMemberList(entity.getId(), QUserEntity.userEntity.id).stream()
.map(String::valueOf).collect(Collectors.joining(","));
if (StringUtils.isNotBlank(userIds)) {
userMessagePublisher.sendUserChangeMessage(UserMessageTag.SAVE, userIds);
}
return true;
}
return false;
@ -458,20 +431,11 @@ public class OrganizationServiceImpl implements OrganizationService {
/**
*
*/
private final OrganizationConverter organizationConverter;
private final OrganizationConverter organizationConverter;
/**
* OrganizationRepository
*/
private final OrganizationRepository organizationRepository;
private final OrganizationRepository organizationRepository;
/**
* MessagePublisher
*/
private final UserMessagePublisher userMessagePublisher;
/**
* OrganizationMemberRepository
*/
private final OrganizationMemberRepository organizationMemberRepository;
}

View File

@ -21,12 +21,10 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.querydsl.QPageRequest;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import com.google.common.collect.Lists;
import com.querydsl.core.types.ExpressionUtils;
@ -49,8 +47,6 @@ import cn.topiam.employee.console.pojo.result.account.UserGroupMemberListResult;
import cn.topiam.employee.console.pojo.save.account.UserGroupCreateParam;
import cn.topiam.employee.console.pojo.update.account.UserGroupUpdateParam;
import cn.topiam.employee.console.service.account.UserGroupService;
import cn.topiam.employee.core.mq.UserMessagePublisher;
import cn.topiam.employee.core.mq.UserMessageTag;
import cn.topiam.employee.support.exception.TopIamException;
import cn.topiam.employee.support.repository.page.domain.Page;
import cn.topiam.employee.support.repository.page.domain.PageModel;
@ -118,14 +114,6 @@ public class UserGroupServiceImpl implements UserGroupService {
UserGroupEntity details = getUserGroup(Long.valueOf(param.getId()));
BeanUtils.merge(entity, details, LAST_MODIFIED_TIME, LAST_MODIFIED_BY);
userGroupRepository.save(details);
// 更新用户es信息
List<UserGroupMemberEntity> userGroupMemberList = userGroupMemberRepository
.findByGroupId(details.getId());
if (!CollectionUtils.isEmpty(userGroupMemberList)) {
userMessagePublisher.sendUserChangeMessage(UserMessageTag.SAVE,
userGroupMemberList.stream().map(item -> String.valueOf(item.getUserId()))
.collect(Collectors.joining(",")));
}
AuditContext.setTarget(
Target.builder().id(details.getId().toString()).type(TargetType.USER_GROUP).build());
return true;
@ -183,8 +171,6 @@ public class UserGroupServiceImpl implements UserGroupService {
public Boolean removeMember(String id, String userId) {
//查询关联关系
userGroupMemberRepository.deleteByGroupIdAndUserId(Long.valueOf(id), Long.valueOf(userId));
// 更新用户es用户组信息
userMessagePublisher.sendUserChangeMessage(UserMessageTag.SAVE, userId);
AuditContext.setTarget(Target.builder().id(userId).type(TargetType.USER).build(),
Target.builder().id(id).type(TargetType.USER_GROUP).build());
return true;
@ -215,8 +201,6 @@ public class UserGroupServiceImpl implements UserGroupService {
});
//添加
userGroupMemberRepository.saveAll(list);
// 更新用户es用户组信息
userMessagePublisher.sendUserChangeMessage(UserMessageTag.SAVE, String.join(",", userIds));
List<Target> targets = new ArrayList<>(Arrays.stream(userIds)
.map(i -> Target.builder().id(i).type(TargetType.USER).build()).toList());
@ -254,8 +238,6 @@ public class UserGroupServiceImpl implements UserGroupService {
}
userIds.forEach(userId -> userGroupMemberRepository
.deleteByGroupIdAndUserId(Long.valueOf(id), Long.valueOf(userId)));
// 更新用户es用户组信息
userMessagePublisher.sendUserChangeMessage(UserMessageTag.SAVE, String.join(",", userIds));
List<Target> targets = new ArrayList<>(userIds.stream()
.map(i -> Target.builder().id(i).type(TargetType.USER).build()).toList());
@ -301,9 +283,4 @@ public class UserGroupServiceImpl implements UserGroupService {
* UserGroupMemberRepository
*/
private final UserGroupMemberRepository userGroupMemberRepository;
/**
* MessagePublisher
*/
private final UserMessagePublisher userMessagePublisher;
}

View File

@ -61,8 +61,6 @@ import cn.topiam.employee.console.service.account.UserService;
import cn.topiam.employee.core.message.MsgVariable;
import cn.topiam.employee.core.message.mail.MailMsgEventPublish;
import cn.topiam.employee.core.message.sms.SmsMsgEventPublish;
import cn.topiam.employee.core.mq.UserMessagePublisher;
import cn.topiam.employee.core.mq.UserMessageTag;
import cn.topiam.employee.support.autoconfiguration.SupportProperties;
import cn.topiam.employee.support.exception.BadParamsException;
import cn.topiam.employee.support.exception.InfoValidityFailException;
@ -200,12 +198,7 @@ public class UserServiceImpl implements UserService {
throw new TopIamException(AuditContext.getContent());
}
AuditContext.setTarget(Target.builder().id(id.toString()).type(TargetType.USER).build());
boolean update = userRepository.updateUserStatus(id, status) > 0;
if (update) {
// 更新索引数据
userMessagePublisher.sendUserChangeMessage(UserMessageTag.SAVE, String.valueOf(id));
}
return update;
return userRepository.updateUserStatus(id, status) > 0;
}
/**
@ -258,9 +251,6 @@ public class UserServiceImpl implements UserService {
organizationMemberRepository.save(member);
AuditContext.setTarget(Target.builder().type(USER).id(user.getId().toString()).build(),
Target.builder().type(USER_DETAIL).id(detail.getId().toString()).build());
// 保存ES用户信息
userMessagePublisher.sendUserChangeMessage(UserMessageTag.SAVE,
String.valueOf(user.getId()));
// 发送短信和邮件的欢迎信息(密码通知)
UserCreateParam.PasswordInitializeConfig passwordInitializeConfig = param
.getPasswordInitializeConfig();
@ -369,9 +359,6 @@ public class UserServiceImpl implements UserService {
userDetailsRepository.save(detail);
AuditContext.setTarget(Target.builder().type(USER).id(user.getId().toString()).build(),
Target.builder().type(USER_DETAIL).id(detail.getId().toString()).build());
// 更新ES用户信息
userMessagePublisher.sendUserChangeMessage(UserMessageTag.SAVE,
String.valueOf(user.getId()));
return true;
}
@ -400,8 +387,6 @@ public class UserServiceImpl implements UserService {
//删除用户组用户详情
userGroupMemberRepository.deleteByUserId(Long.valueOf(id));
AuditContext.setTarget(Target.builder().id(id).type(TargetType.USER).build());
// 删除ES用户信息
userMessagePublisher.sendUserChangeMessage(UserMessageTag.DELETE, id);
return true;
}
@ -445,8 +430,6 @@ public class UserServiceImpl implements UserService {
organizationMemberRepository.deleteAllByUserId(idList);
//删除用户组关系
userGroupMemberRepository.deleteAllByUserId(idList);
// 批量删除ES用户信息
userMessagePublisher.sendUserChangeMessage(UserMessageTag.DELETE, String.join(",", ids));
return true;
}
@ -614,8 +597,4 @@ public class UserServiceImpl implements UserService {
*/
private final PasswordPolicyManager<UserEntity> passwordPolicyManager;
/**
* MessagePublisher
*/
private final UserMessagePublisher userMessagePublisher;
}

View File

@ -19,6 +19,8 @@ package cn.topiam.employee.console.service.analysis;
import java.util.List;
import cn.topiam.employee.audit.repository.result.AuthnQuantityResult;
import cn.topiam.employee.audit.repository.result.AuthnZoneResult;
import cn.topiam.employee.console.pojo.query.analysis.AnalysisQuery;
import cn.topiam.employee.console.pojo.result.analysis.*;
@ -65,7 +67,7 @@ public interface AnalysisService {
*
*
* @param params {@link AnalysisQuery}
* @return {@link List<AuthnZoneResult>}
* @return {@link List< AuthnZoneResult >}
*/
List<AuthnZoneResult> authnZone(AnalysisQuery params);
}

View File

@ -24,14 +24,15 @@ import java.util.*;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.elasticsearch.client.elc.*;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import cn.topiam.employee.audit.entity.AuditElasticSearchEntity;
import cn.topiam.employee.audit.enums.EventStatus;
import cn.topiam.employee.audit.event.type.EventType;
import cn.topiam.employee.authentication.common.IdentityProviderType;
import cn.topiam.employee.audit.repository.AuditRepository;
import cn.topiam.employee.audit.repository.result.AuditStatisticsResult;
import cn.topiam.employee.audit.repository.result.AuthnQuantityResult;
import cn.topiam.employee.audit.repository.result.AuthnZoneResult;
import cn.topiam.employee.common.entity.app.AppEntity;
import cn.topiam.employee.common.repository.account.UserRepository;
import cn.topiam.employee.common.repository.app.AppRepository;
@ -44,17 +45,12 @@ import cn.topiam.employee.support.autoconfiguration.SupportProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import co.elastic.clients.elasticsearch._types.aggregations.*;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders;
import co.elastic.clients.json.JsonData;
import static cn.topiam.employee.audit.entity.Actor.ACTOR_AUTH_TYPE;
import static cn.topiam.employee.audit.entity.Event.*;
import static cn.topiam.employee.audit.entity.GeoLocation.GEO_LOCATION_PROVINCE_CODE;
import static cn.topiam.employee.audit.entity.Target.TARGET_ID_KEYWORD;
import static cn.topiam.employee.common.constant.AuditConstants.getAuditIndexPrefix;
import static cn.topiam.employee.console.converter.authn.IdentityProviderConverter.getIdentityProviderType;
import static cn.topiam.employee.support.constant.EiamConstants.DEFAULT_DATE_TIME_FORMATTER_PATTERN;
@ -76,27 +72,13 @@ public class AnalysisServiceImpl implements AnalysisService {
*/
@Override
public OverviewResult overview() {
IndexCoordinates indexCoordinates = IndexCoordinates
.of(getAuditIndexPrefix(supportProperties.getAudit().getIndexPrefix()) + "*");
// 不存在索引
if (!elasticsearchTemplate.indexOps(indexCoordinates).exists()) {
return new OverviewResult();
}
OverviewResult result = new OverviewResult();
result.setAppCount(appRepository.count());
result.setUserCount(userRepository.count());
result.setIdpCount(identityProviderRepository.count());
// 查询今日认证量条件
Query rangeQuery = QueryBuilders.range(range -> range.field(EVENT_TIME)
.gte(JsonData.of(LocalDateTime.of(LocalDate.now(), LocalTime.MIN)
.format(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMATTER_PATTERN))))
.lt(JsonData.of(LocalDateTime.of(LocalDate.now(), LocalTime.MAX)
.format(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMATTER_PATTERN))))
.timeZone(ZONE_ID).format(DEFAULT_DATE_TIME_FORMATTER_PATTERN));
Query query = getQuery(rangeQuery, EventType.LOGIN_PORTAL);
result.setTodayAuthnCount(
elasticsearchTemplate.count(new NativeQueryBuilder().withQuery(query).build(),
AuditElasticSearchEntity.class, indexCoordinates));
result.setTodayAuthnCount(auditRepository.countByTypeAndTime(
EventType.LOGIN_PORTAL.getCode(), LocalDateTime.MIN, LocalDateTime.MAX));
return result;
}
@ -108,71 +90,11 @@ public class AnalysisServiceImpl implements AnalysisService {
*/
@Override
public List<AuthnQuantityResult> authnQuantity(AnalysisQuery params) {
IndexCoordinates indexCoordinates = IndexCoordinates
.of(getAuditIndexPrefix(supportProperties.getAudit().getIndexPrefix()) + "*");
// 不存在索引
if (!elasticsearchTemplate.indexOps(indexCoordinates).exists()) {
return new ArrayList<>();
}
LocalDateTime min = params.getStartTime();
LocalDateTime max = params.getEndTime();
AnalysisQuery.Interval timeInterval = params.getTimeInterval();
// 根据事件月份分组统计认证数量 count
Aggregation dateGroup = AggregationBuilders
.dateHistogram(count -> count.calendarInterval(timeInterval.getType()).extendedBounds(
fieldDateMathBuilder -> fieldDateMathBuilder.min(FieldDateMath.of(math -> {
math.value(
(double) min.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
return math;
})).max(FieldDateMath.of(math -> {
math.value(
(double) max.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
return math;
}))).field(EVENT_TIME).timeZone(ZONE_ID).format(timeInterval.getFormat()));
// 事件状态group
TermsAggregation statusGroup = AggregationBuilders.terms(t -> t.field(EVENT_STATUS))
.terms();
Aggregation group = new Aggregation.Builder().aggregations("dateGroup", dateGroup)
.terms(statusGroup).build();
return getAuthnQuantityResults(indexCoordinates, min, max, group);
}
@NotNull
private List<AuthnQuantityResult> getAuthnQuantityResults(IndexCoordinates indexCoordinates,
LocalDateTime min, LocalDateTime max,
Aggregation aggregation) {
// 查询条件
Query rangeBuilder = QueryBuilders.range(range -> range.field(EVENT_TIME).timeZone(ZONE_ID)
.format(DEFAULT_DATE_TIME_FORMATTER_PATTERN)
.gt(JsonData
.of(min.format(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMATTER_PATTERN))))
.lt(JsonData
.of(max.format(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMATTER_PATTERN)))));
Query query = getQuery(rangeBuilder, EventType.LOGIN_PORTAL);
NativeQuery authCountBuild = new NativeQueryBuilder().withQuery(query)
.withAggregation(COUNT, aggregation).withMaxResults(0).build();
// 统计认证量
SearchHits<AuditElasticSearchEntity> authCountResult = elasticsearchTemplate
.search(authCountBuild, AuditElasticSearchEntity.class, indexCoordinates);
ElasticsearchAggregation countGroupAggregation = getCountAggregation(authCountResult);
List<AuthnQuantityResult> authCountList = new ArrayList<>();
if (countGroupAggregation != null) {
List<StringTermsBucket> buckets = countGroupAggregation.aggregation().getAggregate()
.sterms().buckets().array();
for (StringTermsBucket bucket : buckets) {
// success/fail
String statusGroupKey = bucket.key().stringValue();
List<DateHistogramBucket> dateGroupList = bucket.aggregations().get("dateGroup")
.dateHistogram().buckets().array();
for (DateHistogramBucket dateGroup : dateGroupList) {
String dateGroupKey = dateGroup.keyAsString();
authCountList.add(new AuthnQuantityResult(dateGroupKey, dateGroup.docCount(),
Objects.requireNonNull(EventStatus.getType(statusGroupKey)).getDesc()));
}
}
}
return authCountList;
return auditRepository.authnQuantity(EventType.LOGIN_PORTAL, min, max,
timeInterval.getFormat());
}
/**
@ -183,32 +105,13 @@ public class AnalysisServiceImpl implements AnalysisService {
*/
@Override
public List<AppVisitRankResult> appVisitRank(AnalysisQuery params) {
IndexCoordinates indexCoordinates = IndexCoordinates
.of(getAuditIndexPrefix(supportProperties.getAudit().getIndexPrefix()) + "*");
// 不存在索引
if (!elasticsearchTemplate.indexOps(indexCoordinates).exists()) {
return new ArrayList<>();
}
Query rangeQuery = getRangeQueryBuilder(params);
Query query = getQuery(rangeQuery, EventType.APP_SSO);
// 应用访问频次前10条
Aggregation groupAppVisit = AggregationBuilders
.terms(terms -> terms.field(TARGET_ID_KEYWORD).size(10));
NativeQuery appVisitBuild = new NativeQueryBuilder().withQuery(query)
.withAggregation(COUNT, groupAppVisit).build();
SearchHits<AuditElasticSearchEntity> appVisitResult = elasticsearchTemplate
.search(appVisitBuild, AuditElasticSearchEntity.class, indexCoordinates);
ElasticsearchAggregation countAggregation = getCountAggregation(appVisitResult);
List<AppVisitRankResult> applicationVisitList = new ArrayList<>();
if (countAggregation != null) {
List<StringTermsBucket> array = countAggregation.aggregation().getAggregate().sterms()
.buckets().array();
for (StringTermsBucket bucket : array) {
String key = bucket.key().stringValue();
// 单点登录
String name = getAppName(key);
applicationVisitList.add(new AppVisitRankResult(name, bucket.docCount()));
}
List<AuditStatisticsResult> auditRankResults = auditRepository
.appVisitRank(EventType.APP_SSO, params.getStartTime(), params.getEndTime());
for (AuditStatisticsResult auditRankResult : auditRankResults) {
// 单点登录
String name = getAppName(auditRankResult.getKey());
applicationVisitList.add(new AppVisitRankResult(name, auditRankResult.getCount()));
}
return applicationVisitList;
}
@ -236,33 +139,14 @@ public class AnalysisServiceImpl implements AnalysisService {
*/
@Override
public List<AuthnHotProviderResult> authnHotProvider(AnalysisQuery params) {
IndexCoordinates indexCoordinates = IndexCoordinates
.of(getAuditIndexPrefix(supportProperties.getAudit().getIndexPrefix()) + "*");
// 不存在索引
if (!elasticsearchTemplate.indexOps(indexCoordinates).exists()) {
return new ArrayList<>();
}
Query builder = getRangeQueryBuilder(params);
BoolQuery.Builder queryBuilder = getQueryBuilder(builder, EventType.LOGIN_PORTAL);
queryBuilder.must(QueryBuilders.exists(e -> e.field(ACTOR_AUTH_TYPE)));
// 授权类型频次
Aggregation groupAuthType = AggregationBuilders
.terms(terms -> terms.field(ACTOR_AUTH_TYPE).size(IdentityProviderType.size()));
NativeQuery appVisitBuild = new NativeQueryBuilder()
.withQuery(queryBuilder.build()._toQuery()).withAggregation(COUNT, groupAuthType)
.build();
SearchHits<AuditElasticSearchEntity> authTypeResult = elasticsearchTemplate
.search(appVisitBuild, AuditElasticSearchEntity.class, indexCoordinates);
ElasticsearchAggregation authTypeStringTerms = getCountAggregation(authTypeResult);
List<AuthnHotProviderResult> authTypeList = new ArrayList<>();
if (authTypeStringTerms != null) {
List<StringTermsBucket> array = authTypeStringTerms.aggregation().getAggregate()
.sterms().buckets().array();
for (StringTermsBucket bucket : array) {
String key = bucket.key().stringValue();
// 授权类型
String name = getIdentityProviderType(key).name();
authTypeList.add(new AuthnHotProviderResult(name, bucket.docCount()));
List<AuditStatisticsResult> auditRankResults = auditRepository
.authnHotProvider(EventType.LOGIN_PORTAL, params.getStartTime(), params.getEndTime());
for (AuditStatisticsResult auditRankResult : auditRankResults) {
// 授权类型
if (Objects.nonNull(auditRankResult.getKey())) {
String name = getIdentityProviderType(auditRankResult.getKey()).name();
authTypeList.add(new AuthnHotProviderResult(name, auditRankResult.getCount()));
}
}
return authTypeList;
@ -272,38 +156,16 @@ public class AnalysisServiceImpl implements AnalysisService {
*
*
* @param params {@link AnalysisQuery}
* @return {@link List<AuthnZoneResult>}
* @return {@link List< AuthnZoneResult >}
*/
@Override
public List<AuthnZoneResult> authnZone(AnalysisQuery params) {
IndexCoordinates indexCoordinates = IndexCoordinates
.of(getAuditIndexPrefix(supportProperties.getAudit().getIndexPrefix()) + "*");
// 不存在索引
if (!elasticsearchTemplate.indexOps(indexCoordinates).exists()) {
return new ArrayList<>();
}
Query builder = getRangeQueryBuilder(params);
BoolQuery.Builder queryBuilder = getQueryBuilder(builder, EventType.LOGIN_PORTAL);
queryBuilder.must(QueryBuilders.exists(exists -> exists.field(GEO_LOCATION_PROVINCE_CODE)));
// 登录城市分组统计
Aggregation groupAuthZone = AggregationBuilders
.terms(terms -> terms.field(GEO_LOCATION_PROVINCE_CODE).size(36));
NativeQuery appVisitBuild = new NativeQueryBuilder()
.withQuery(queryBuilder.build()._toQuery()).withAggregation(COUNT, groupAuthZone)
.build();
SearchHits<AuditElasticSearchEntity> authZoneResult = elasticsearchTemplate
.search(appVisitBuild, AuditElasticSearchEntity.class, indexCoordinates);
ElasticsearchAggregation authZoneStringTerms = getCountAggregation(authZoneResult);
List<AuthnZoneResult> authnZoneResults = new ArrayList<>();
if (authZoneStringTerms != null) {
List<StringTermsBucket> array = authZoneStringTerms.aggregation().getAggregate()
.sterms().buckets().array();
for (StringTermsBucket bucket : array) {
String key = bucket.key().stringValue();
authnZoneResults.add(new AuthnZoneResult(key, bucket.docCount()));
}
}
return authnZoneResults;
List<AuditStatisticsResult> auditStatisticsResults = auditRepository
.authnZone(EventType.LOGIN_PORTAL, params.getStartTime(), params.getEndTime());
return auditStatisticsResults.stream()
.map(auditStatisticsResult -> new AuthnZoneResult(auditStatisticsResult.getKey(),
auditStatisticsResult.getCount()))
.toList();
}
/**
@ -372,7 +234,9 @@ public class AnalysisServiceImpl implements AnalysisService {
private final SupportProperties supportProperties;
private final ElasticsearchTemplate elasticsearchTemplate;
// private final ElasticsearchTemplate elasticsearchTemplate;
private final AuditRepository auditRepository;
private final AppRepository appRepository;

View File

@ -65,7 +65,7 @@ import cn.topiam.employee.support.util.PhoneNumberUtils;
import cn.topiam.employee.support.validation.annotation.ValidationPhone;
import lombok.extern.slf4j.Slf4j;
import static cn.topiam.employee.console.access.DefaultAdministratorConstants.DEFAULT_ADMIN_USERNAME;
import static cn.topiam.employee.common.constant.SecurityConstants.DEFAULT_ADMIN_USERNAME;
import static cn.topiam.employee.support.util.PhoneNumberUtils.getPhoneNumber;
/**

View File

@ -1,5 +1,5 @@
/*
* eiam-synchronizer - Employee Identity and Access Management
* eiam-console - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.synchronizer.configuration;
package cn.topiam.employee.console.synchronizer.configuration;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@ -47,6 +47,7 @@ import cn.topiam.employee.common.entity.identitysource.IdentitySourceEntity;
import cn.topiam.employee.common.enums.TriggerType;
import cn.topiam.employee.common.enums.identitysource.IdentitySourceProvider;
import cn.topiam.employee.common.repository.identitysource.IdentitySourceRepository;
import cn.topiam.employee.console.synchronizer.task.IdentitySourceSyncTask;
import cn.topiam.employee.identitysource.core.IdentitySource;
import cn.topiam.employee.identitysource.core.IdentitySourceConfig;
import cn.topiam.employee.identitysource.core.client.IdentitySourceClient;
@ -64,14 +65,12 @@ import cn.topiam.employee.identitysource.feishu.client.FeiShuClient;
import cn.topiam.employee.identitysource.wechatwork.WeChatWorkConfig;
import cn.topiam.employee.identitysource.wechatwork.WeChatWorkIdentitySource;
import cn.topiam.employee.identitysource.wechatwork.client.WeChatWorkClient;
import cn.topiam.employee.support.scheduler.SpringSchedulerRegistrar;
import cn.topiam.employee.support.scheduler.SpringSchedulerRegister;
import cn.topiam.employee.support.trace.TraceUtils;
import cn.topiam.employee.synchronizer.task.IdentitySourceSyncTask;
import lombok.extern.slf4j.Slf4j;
import static cn.topiam.employee.common.enums.identitysource.IdentitySourceProvider.DINGTALK;
import static cn.topiam.employee.support.lock.LockAspect.getTopiamLockKeyPrefix;
import static cn.topiam.employee.synchronizer.configuration.IdentitySourceBeanUtils.getSourceBeanName;
/**
* Bean
@ -124,7 +123,8 @@ public class IdentitySourceBeanRegistry implements IdentitySourceEventListener {
.getBeanFactory();
//如果已经存在,销毁
try {
if (ObjectUtils.isNotEmpty(beanFactory.getBean(getSourceBeanName(id)))) {
if (ObjectUtils
.isNotEmpty(beanFactory.getBean(IdentitySourceBeanUtils.getSourceBeanName(id)))) {
destroyIdentitySourceBean(id, applicationContext);
}
} catch (NoSuchBeanDefinitionException ignored) {
@ -200,7 +200,7 @@ public class IdentitySourceBeanRegistry implements IdentitySourceEventListener {
//设置为 RefreshScope
definitionBuilder.setScope("refresh");
return new BeanDefinitionHolder(definitionBuilder.getBeanDefinition(),
getSourceBeanName(entity.getId().toString()));
IdentitySourceBeanUtils.getSourceBeanName(entity.getId().toString()));
}
/**
@ -213,7 +213,7 @@ public class IdentitySourceBeanRegistry implements IdentitySourceEventListener {
ApplicationContext applicationContext) {
BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) ((ConfigurableApplicationContext) applicationContext)
.getBeanFactory();
String beanName = getSourceBeanName(id);
String beanName = IdentitySourceBeanUtils.getSourceBeanName(id);
try {
beanDefinitionRegistry.removeBeanDefinition(beanName);
} catch (NoSuchBeanDefinitionException ignored) {
@ -229,11 +229,11 @@ public class IdentitySourceBeanRegistry implements IdentitySourceEventListener {
public static void registerIdentitySourceSyncTask(IdentitySourceEntity entity,
ApplicationContext applicationContext) {
String id = entity.getId().toString();
String beanName = getSourceBeanName(id);
String beanName = IdentitySourceBeanUtils.getSourceBeanName(id);
IdentitySource<? extends IdentitySourceConfig> identitySource = (IdentitySource<? extends IdentitySourceConfig>) applicationContext
.getBean(beanName);
SpringSchedulerRegistrar schedulerRegistrarHelp = applicationContext
.getBean(SpringSchedulerRegistrar.class);
SpringSchedulerRegister schedulerRegistrarHelp = applicationContext
.getBean(SpringSchedulerRegister.class);
RedissonClient redissonClient = applicationContext.getBean(RedissonClient.class);
//注册定时任务
String cronExpression = entity.getJobConfig().getCronExpression(CronType.SPRING);
@ -252,8 +252,8 @@ public class IdentitySourceBeanRegistry implements IdentitySourceEventListener {
*/
public static void destroyIdentitySourceSyncTask(String id,
ApplicationContext applicationContext) {
SpringSchedulerRegistrar schedulerRegistrarHelp = applicationContext
.getBean(SpringSchedulerRegistrar.class);
SpringSchedulerRegister schedulerRegistrarHelp = applicationContext
.getBean(SpringSchedulerRegister.class);
schedulerRegistrarHelp.removeCronTask(id);
}
@ -304,7 +304,7 @@ public class IdentitySourceBeanRegistry implements IdentitySourceEventListener {
@Override
public void sync(String id) {
IdentitySource<? extends IdentitySourceConfig> identitySource = (IdentitySource<? extends IdentitySourceConfig>) applicationContext
.getBean(getSourceBeanName(id));
.getBean(IdentitySourceBeanUtils.getSourceBeanName(id));
RedissonClient redissonClient = applicationContext.getBean(RedissonClient.class);
StopWatch stopWatch = new StopWatch();
stopWatch.start();

View File

@ -1,5 +1,5 @@
/*
* eiam-synchronizer - Employee Identity and Access Management
* eiam-console - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.synchronizer.configuration;
package cn.topiam.employee.console.synchronizer.configuration;
import org.apache.commons.codec.digest.DigestUtils;

View File

@ -0,0 +1,18 @@
/*
* eiam-console - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console.synchronizer.configuration;

View File

@ -1,5 +1,5 @@
/*
* eiam-synchronizer - Employee Identity and Access Management
* eiam-console - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.synchronizer.endpoint;
package cn.topiam.employee.console.synchronizer.endpoint;
import java.util.Optional;
@ -28,15 +28,15 @@ import org.springframework.web.bind.annotation.RestController;
import cn.topiam.employee.common.entity.identitysource.IdentitySourceEntity;
import cn.topiam.employee.common.repository.identitysource.IdentitySourceRepository;
import cn.topiam.employee.console.synchronizer.configuration.IdentitySourceBeanUtils;
import cn.topiam.employee.identitysource.core.IdentitySource;
import cn.topiam.employee.identitysource.core.IdentitySourceConfig;
import cn.topiam.employee.support.trace.Trace;
import cn.topiam.employee.synchronizer.configuration.IdentitySourceBeanUtils;
import lombok.extern.slf4j.Slf4j;
import jakarta.servlet.http.HttpServletRequest;
import static cn.topiam.employee.synchronizer.constants.SynchronizerConstants.EVENT_RECEIVE_PATH;
import static cn.topiam.employee.common.constant.SynchronizerConstants.EVENT_RECEIVE_PATH;
/**
*

View File

@ -0,0 +1,18 @@
/*
* eiam-console - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console.synchronizer.endpoint;

View File

@ -15,4 +15,4 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console.listener;
package cn.topiam.employee.console.synchronizer;

View File

@ -1,5 +1,5 @@
/*
* eiam-synchronizer - Employee Identity and Access Management
* eiam-console - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.synchronizer.processor;
package cn.topiam.employee.console.synchronizer.processor;
import java.io.InputStream;
import java.net.URL;

View File

@ -1,5 +1,5 @@
/*
* eiam-synchronizer - Employee Identity and Access Management
* eiam-console - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.synchronizer.processor;
package cn.topiam.employee.console.synchronizer.processor;
import java.io.Serial;
import java.io.Serializable;

View File

@ -1,5 +1,5 @@
/*
* eiam-synchronizer - Employee Identity and Access Management
* eiam-console - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.synchronizer.processor;
package cn.topiam.employee.console.synchronizer.processor;
import java.time.LocalDateTime;
import java.util.*;
@ -55,8 +55,6 @@ import cn.topiam.employee.common.repository.identitysource.IdentitySourceSyncRec
import cn.topiam.employee.common.storage.Storage;
import cn.topiam.employee.core.message.mail.MailMsgEventPublish;
import cn.topiam.employee.core.message.sms.SmsMsgEventPublish;
import cn.topiam.employee.core.mq.UserMessagePublisher;
import cn.topiam.employee.core.mq.UserMessageTag;
import cn.topiam.employee.identitysource.core.domain.Dept;
import cn.topiam.employee.identitysource.core.domain.User;
import cn.topiam.employee.identitysource.core.enums.IdentitySourceEventReceiveType;
@ -133,7 +131,6 @@ public class DefaultIdentitySourceEventPostProcessor extends AbstractIdentitySou
List<IdentitySourceEventRecordEntity> records = new ArrayList<>();
List<UserEntity> createUsers = new ArrayList<>();
Set<OrganizationMemberEntity> createOrganizationMembers = Sets.newHashSet();
List<String> batchEsUserIdList = new ArrayList<>();
users.forEach(thirdPartyUser -> {
log.info("处理上游用户新增事件:[{}]开始", thirdPartyUser.getUserId());
UserEntity userEntity = thirdPartyUserToUserEntity(thirdPartyUser, identitySource);
@ -149,7 +146,6 @@ public class DefaultIdentitySourceEventPostProcessor extends AbstractIdentitySou
userEntity.getId());
createOrganizationMember(createOrganizationMembers, userEntity, organization);
});
batchEsUserIdList.add(String.valueOf(userEntity.getId()));
//记录日志
IdentitySourceEventRecordEntity record = new IdentitySourceEventRecordEntity();
record.setObjectId(userEntity.getId().toString());
@ -174,9 +170,6 @@ public class DefaultIdentitySourceEventPostProcessor extends AbstractIdentitySou
organizationMemberRepository.batchSave(Lists.newArrayList(createOrganizationMembers));
//保存事件记录
identitySourceEventRecordRepository.batchSave(records);
// 异步创建ES用户数据
userMessagePublisher.sendUserChangeMessage(UserMessageTag.SAVE,
String.join(",", batchEsUserIdList));
// 批量发送短信邮件欢迎信息(密码通知)
publishMessage(createUsers);
}
@ -192,7 +185,6 @@ public class DefaultIdentitySourceEventPostProcessor extends AbstractIdentitySou
List<IdentitySourceEventRecordEntity> records = new ArrayList<>();
List<UserEntity> updateUsers = new ArrayList<>();
List<OrganizationMemberEntity> createOrganizationMembers = new ArrayList<>();
List<String> batchEsUserIdList = new ArrayList<>();
//需要删除组织成员关系集合 KEY用户IDvalue组织ID
Map<Long, Set<String>> deleteOrganizationMembers = Maps.newHashMap();
Map<UserEntity, Set<OrganizationEntity>> currentUsers = Maps.newHashMap();
@ -252,9 +244,6 @@ public class DefaultIdentitySourceEventPostProcessor extends AbstractIdentitySou
record.setUpdateBy(SYSTEM_DEFAULT_USER_NAME);
record.setUpdateTime(LocalDateTime.now());
records.add(record);
// 构建ES用户Id信息
batchEsUserIdList.add(String.valueOf(currentUser.getId()));
log.info("处理上游用户修改事件:[{}]结束", thirdPartyUser.getUserId());
}
}));
@ -267,8 +256,6 @@ public class DefaultIdentitySourceEventPostProcessor extends AbstractIdentitySou
.forEach(user -> deleteOrganizationMembers.get(user)
.forEach(organization -> organizationMemberRepository
.deleteByOrgIdAndUserId(organization, user)));
// 异步更新ES用户数据
userMessagePublisher.sendUserChangeMessage(UserMessageTag.SAVE, String.join(",", batchEsUserIdList));
//保存事件记录
identitySourceEventRecordRepository.batchSave(records);
//@formatter:on
@ -398,7 +385,6 @@ public class DefaultIdentitySourceEventPostProcessor extends AbstractIdentitySou
//根据外部ID查询
List<OrganizationEntity> list = organizationRepository
.findByExternalIdIn(organizations.stream().map(Dept::getDeptId).toList());
List<String> userIds = new ArrayList<>();
list.forEach(current -> organizations.forEach(threeParty -> {
//当前三方ID和上游科室对应
if (current.getExternalId().equals(threeParty.getDeptId())) {
@ -434,20 +420,10 @@ public class DefaultIdentitySourceEventPostProcessor extends AbstractIdentitySou
if (subOrganizations.stream().map(i -> i.getId().equals(current.getId()))
.findAny().isEmpty()) {
organizationRepository.updateIsLeaf(parentOrganization.getId(), true);
// 查询关联用户
List<OrganizationMemberEntity> orgMemberList = organizationMemberRepository
.findAllByOrgId(parentOrganization.getId());
userIds.addAll(orgMemberList.stream()
.map(item -> String.valueOf(item.getUserId())).toList());
}
}
if (parentOrganization.getLeaf()) {
organizationRepository.updateIsLeaf(parentOrganization.getId(), false);
// 查询关联用户
List<OrganizationMemberEntity> orgMemberList = organizationMemberRepository
.findAllByOrgId(parentOrganization.getId());
userIds.addAll(orgMemberList.stream()
.map(item -> String.valueOf(item.getUserId())).toList());
}
//修改基本信息
current.setName(threeParty.getName());
@ -469,17 +445,10 @@ public class DefaultIdentitySourceEventPostProcessor extends AbstractIdentitySou
record.setUpdateBy(SYSTEM_DEFAULT_USER_NAME);
record.setUpdateTime(LocalDateTime.now());
records.add(record);
// 查询关联用户
List<OrganizationMemberEntity> orgMemberList = organizationMemberRepository
.findAllByOrgId(current.getId());
userIds.addAll(
orgMemberList.stream().map(item -> String.valueOf(item.getUserId())).toList());
}
}));
//批量保存部门信息
organizationRepository.batchUpdate(updateOrganizations);
// 更新es用户信息
userMessagePublisher.sendUserChangeMessage(UserMessageTag.SAVE, String.join(",", userIds));
//保存事件记录
identitySourceEventRecordRepository.batchSave(records);
}
@ -554,11 +523,6 @@ public class DefaultIdentitySourceEventPostProcessor extends AbstractIdentitySou
*/
private final IdentitySourceEventRecordRepository identitySourceEventRecordRepository;
/**
* MessagePublisher
*/
private final UserMessagePublisher userMessagePublisher;
public DefaultIdentitySourceEventPostProcessor(SmsMsgEventPublish smsMsgEventPublish,
MailMsgEventPublish mailMsgEventPublish,
PasswordEncoder passwordEncoder,
@ -573,8 +537,7 @@ public class DefaultIdentitySourceEventPostProcessor extends AbstractIdentitySou
OrganizationRepository organizationRepository,
OrganizationMemberRepository organizationMemberRepository,
IdentitySourceEventRecordRepository identitySourceEventRecordRepository,
Storage storage,
UserMessagePublisher userMessagePublisher) {
Storage storage) {
super(smsMsgEventPublish, mailMsgEventPublish, passwordEncoder, passwordGenerator,
transactionDefinition, platformTransactionManager, entityManager,
identitySourceRepository, identitySourceSyncHistoryRepository,
@ -583,6 +546,5 @@ public class DefaultIdentitySourceEventPostProcessor extends AbstractIdentitySou
this.organizationRepository = organizationRepository;
this.organizationMemberRepository = organizationMemberRepository;
this.identitySourceEventRecordRepository = identitySourceEventRecordRepository;
this.userMessagePublisher = userMessagePublisher;
}
}

View File

@ -1,5 +1,5 @@
/*
* eiam-synchronizer - Employee Identity and Access Management
* eiam-console - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,13 +15,12 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.synchronizer.processor;
package cn.topiam.employee.console.synchronizer.processor;
import java.io.Serial;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
@ -54,8 +53,6 @@ import cn.topiam.employee.common.repository.identitysource.IdentitySourceSyncRec
import cn.topiam.employee.common.storage.Storage;
import cn.topiam.employee.core.message.mail.MailMsgEventPublish;
import cn.topiam.employee.core.message.sms.SmsMsgEventPublish;
import cn.topiam.employee.core.mq.UserMessagePublisher;
import cn.topiam.employee.core.mq.UserMessageTag;
import cn.topiam.employee.identitysource.core.domain.User;
import cn.topiam.employee.identitysource.core.processor.IdentitySourceSyncUserPostProcessor;
import cn.topiam.employee.support.repository.domain.IdEntity;
@ -100,19 +97,14 @@ public class DefaultIdentitySourceUserPostProcessor extends AbstractIdentitySour
processData = processData(identitySource, initData);
//校验数据
validateData(processData);
String batchEsUserId = "";
//批量保存用户
if (!CollectionUtils.isEmpty(processData.getCreateUsers())) {
List<UserEntity> createUserList = Lists.newArrayList(processData.getCreateUsers());
batchEsUserId += createUserList.stream().map(user -> String.valueOf(user.getId()))
.collect(Collectors.joining(","));
userRepository.batchSave(createUserList);
}
//批量更新用户
if (!CollectionUtils.isEmpty(processData.getUpdateUsers())) {
List<UserEntity> updateUserList = Lists.newArrayList(processData.getUpdateUsers());
batchEsUserId += updateUserList.stream().map(user -> String.valueOf(user.getId()))
.collect(Collectors.joining(","));
userRepository.batchUpdate(updateUserList);
}
//保存组织成员关系
@ -148,13 +140,6 @@ public class DefaultIdentitySourceUserPostProcessor extends AbstractIdentitySour
saveSyncHistoryRecord(history.getId(), processData);
//提交事务
platformTransactionManager.commit(transactionStatus);
// 异步更新ES用户数据
userMessagePublisher.sendUserChangeMessage(UserMessageTag.SAVE, batchEsUserId);
// 异步删除用户ES数据
if (!CollectionUtils.isEmpty(processData.getDeleteUsers())) {
userMessagePublisher.sendUserChangeMessage(UserMessageTag.DELETE,
deleteUserIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
}
if (!CollectionUtils.isEmpty(processData.getCreateUsers())) {
// 发送密码通知
publishMessage(Lists.newArrayList(processData.getCreateUsers()));
@ -734,11 +719,6 @@ public class DefaultIdentitySourceUserPostProcessor extends AbstractIdentitySour
*/
private final UserGroupMemberRepository userGroupMemberRepository;
/**
* MessagePublisher
*/
private final UserMessagePublisher userMessagePublisher;
public DefaultIdentitySourceUserPostProcessor(SmsMsgEventPublish smsMsgEventPublish,
MailMsgEventPublish mailMsgEventPublish,
TransactionDefinition transactionDefinition,
@ -754,8 +734,7 @@ public class DefaultIdentitySourceUserPostProcessor extends AbstractIdentitySour
OrganizationMemberRepository organizationMemberRepository,
UserGroupMemberRepository userGroupMemberRepository,
OrganizationRepository organizationRepository,
Storage storage,
UserMessagePublisher userMessagePublisher) {
Storage storage) {
super(smsMsgEventPublish, mailMsgEventPublish, passwordEncoder, passwordGenerator,
transactionDefinition, platformTransactionManager, entityManager,
identitySourceRepository, identitySourceSyncHistoryRepository,
@ -765,6 +744,5 @@ public class DefaultIdentitySourceUserPostProcessor extends AbstractIdentitySour
this.organizationMemberRepository = organizationMemberRepository;
this.userGroupMemberRepository = userGroupMemberRepository;
this.organizationRepository = organizationRepository;
this.userMessagePublisher = userMessagePublisher;
}
}

View File

@ -1,5 +1,5 @@
/*
* eiam-synchronizer - Employee Identity and Access Management
* eiam-console - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
@ -15,7 +15,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.synchronizer.task;
package cn.topiam.employee.console.synchronizer.task;
import java.util.concurrent.TimeUnit;

View File

@ -15,4 +15,4 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console.constant;
package cn.topiam.employee.console.synchronizer.task;

View File

@ -132,10 +132,6 @@ spring:
overwrite-existing-jobs: true
jdbc:
initialize-schema: never
#rabbitmq
rabbitmq:
template:
reply-timeout: 60000
#日志配置
logging:
config: classpath:config/logback-spring.xml

View File

@ -1,50 +0,0 @@
/*
* eiam-console - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee;
import cn.topiam.employee.support.geo.GeoLocation;
import cn.topiam.employee.support.geo.GeoLocationService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@SpringBootTest
public class GeoLocationServiceTest {
private final Logger logger= LoggerFactory.getLogger(GeoLocationServiceTest.class);
@Test
public void getGeoLocation() throws JsonProcessingException {
GeoLocation geoLocation = geoLocationService.getGeoLocation("119.163.76.166");
logger.info("Test 119.163.76.166 GeoLocation : {}", objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(geoLocation));
}
@Autowired
private ObjectMapper objectMapper;
@Autowired
private GeoLocationService geoLocationService;
}

View File

@ -1,114 +0,0 @@
/*
* eiam-core - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.core.dynamic;
import java.util.List;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.util.CollectionUtils;
import cn.topiam.employee.common.entity.account.UserElasticSearchEntity;
import cn.topiam.employee.common.entity.account.po.UserEsPO;
import cn.topiam.employee.common.repository.account.OrganizationRepository;
import cn.topiam.employee.common.repository.account.UserElasticSearchRepository;
import cn.topiam.employee.common.repository.account.UserRepository;
import cn.topiam.employee.support.autoconfiguration.SupportProperties;
import cn.topiam.employee.support.lock.Lock;
import cn.topiam.employee.support.trace.Trace;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import static cn.topiam.employee.core.mq.UserMessageListener.getUserElasticSearchEntity;
/**
* UserSyncTask
*
* @author TopIAM
* Created by support@topiam.cn on 2023/6/18 21:42
*/
@Slf4j
@RequiredArgsConstructor
public class UserSyncTask {
@Lock(throwException = false)
@Trace
@Scheduled(fixedRate = 60000)
public void syncUser() {
try {
long start = System.currentTimeMillis();
log.info("同步用户数据到ES开始");
IndexCoordinates userIndex = IndexCoordinates
.of(supportProperties.getUser().getIndexPrefix());
// 查询库中全部用户信息
List<UserEsPO> userList = userRepository.getUserList(null);
List<UserElasticSearchEntity> newUserElasticSearch = getUserElasticSearchEntity(
userList, organizationRepository);
List<UserElasticSearchEntity> saveUserList;
int removeNumber = 0;
// 存在索引查询es用户数据
List<UserElasticSearchEntity> oldUserElasticSearchList = userRepository
.getAllUserElasticSearchEntity(userIndex);
if (!CollectionUtils.isEmpty(oldUserElasticSearchList)) {
// 要删除的数据
List<String> removeUserIdList = oldUserElasticSearchList.stream()
.map(UserElasticSearchEntity::getId).filter(id -> newUserElasticSearch.stream()
.noneMatch(newUser -> newUser.getId().equals(id)))
.toList();
removeNumber = removeUserIdList.size();
// 删除
if (!CollectionUtils.isEmpty(removeUserIdList)) {
userElasticSearchRepository.deleteAllById(removeUserIdList);
}
// 要更新的数据
saveUserList = newUserElasticSearch.stream()
.filter(newUser -> oldUserElasticSearchList.stream()
.noneMatch(oldUser -> oldUser.equals(newUser)))
.toList();
} else {
saveUserList = newUserElasticSearch;
}
// 更新
userElasticSearchRepository.saveAll(saveUserList);
log.info("同步用户数据到ES成功, 更新:[{}], 删除:[{}], 耗时:[{}]s", saveUserList.size(), removeNumber,
(System.currentTimeMillis() - start) / 1000);
} catch (Exception e) {
log.error("同步用户数据到ES异常:[{}]", e.getMessage(), e);
}
}
/**
* SupportProperties
*/
private final SupportProperties supportProperties;
/**
* UserElasticSearchRepository
*/
private final UserElasticSearchRepository userElasticSearchRepository;
/**
* UserRepository
*/
private final UserRepository userRepository;
/**
* OrganizationRepository
*/
private final OrganizationRepository organizationRepository;
}

View File

@ -15,23 +15,41 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.core.dynamic;
package cn.topiam.employee.core.message.mail;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
import cn.topiam.employee.support.autoconfiguration.SupportProperties;
import org.springframework.context.ApplicationEvent;
import cn.topiam.employee.common.enums.MailType;
import lombok.Getter;
/**
* ElasticsearchConfiguration
*
*
* @author TopIAM
* Created by support@topiam.cn on 2022/11/3 23:31
* Created by support@topiam.cn on 2021/9/25 21:07
*/
@Configuration
public class UserElasticsearchConfiguration {
@Bean
public UserIndexName userIndexName(SupportProperties supportProperties) {
return new UserIndexName(supportProperties);
@Getter
public class MailMsgEvent extends ApplicationEvent {
/**
*
*/
private final MailType type;
/**
*
*/
private final String receiver;
/**
*
*/
private final Map<String, Object> parameter;
public MailMsgEvent(MailType type, String receiver, Map<String, Object> parameter) {
super(type);
this.type = type;
this.receiver = receiver;
this.parameter = parameter;
}
}

View File

@ -0,0 +1,194 @@
/*
* eiam-core - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.core.message.mail;
import java.io.IOException;
import java.io.StringWriter;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import cn.topiam.employee.common.entity.message.MailSendRecordEntity;
import cn.topiam.employee.common.entity.setting.MailTemplateEntity;
import cn.topiam.employee.common.enums.MailType;
import cn.topiam.employee.common.exception.MailMessageSendException;
import cn.topiam.employee.common.message.mail.MailProviderSend;
import cn.topiam.employee.common.message.mail.SendMailRequest;
import cn.topiam.employee.common.repository.message.MailSendRecordRepository;
import cn.topiam.employee.core.message.MsgVariable;
import cn.topiam.employee.core.setting.constant.MessageSettingConstants;
import freemarker.cache.StringTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import static org.apache.commons.codec.CharEncoding.UTF_8;
import static org.springframework.web.util.HtmlUtils.htmlUnescape;
import static cn.topiam.employee.support.constant.EiamConstants.COLON;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2021/9/25 21:07
*/
@Async
@Component
public class MailMsgEventListener implements ApplicationListener<MailMsgEvent> {
private final Logger logger = LoggerFactory.getLogger(MailMsgEventListener.class);
@Override
public void onApplicationEvent(@NonNull MailMsgEvent event) {
// 邮件通知类型
MailType type = event.getType();
String content = htmlUnescape(MailUtils.readEmailContent(type.getContent()));
String subject = type.getSubject();
String sender = type.getSender();
try {
Map<String, Object> parameter = event.getParameter();
// 判断是否存在自定义
MailTemplateEntity templateEntity = (MailTemplateEntity) redisTemplate.opsForValue()
.get(MessageSettingConstants.SETTING_EMAIL_TEMPLATE_CACHE_NAME + COLON
+ type.getCode());
if (!Objects.isNull(templateEntity)) {
content = htmlUnescape(templateEntity.getContent());
subject = templateEntity.getSubject();
sender = templateEntity.getSender();
}
// 主题
StringWriter themeStringWriter = new StringWriter();
Template themeTpl = createTemplate(type.getCode() + "-theme", subject);
themeTpl.process(parameter, themeStringWriter);
// 测试邮件
if (parameter.containsKey(MsgVariable.TEST)) {
String test = parameter.get(MsgVariable.TEST).toString();
themeStringWriter.append(test);
}
// 内容
StringWriter contentStringWriter = new StringWriter();
Template contentTpl = createTemplate(type.getCode() + "-content", content);
contentTpl.process(parameter, contentStringWriter);
// 发送邮件
//@formatter:off
SendMailRequest params = new SendMailRequest()
// 发送人
.setSender(sender)
// 接收人
.setReceiver(event.getReceiver())
// 主题
.setSubject(themeStringWriter.toString())
// 内容
.setBody(contentStringWriter.toString());
//@formatter:on
// 发送邮件
mailProviderSend.sendMailHtml(params);
// 保存发送记录
MailSendRecordEntity record = new MailSendRecordEntity();
//@formatter:off
record.setSender(event.getReceiver())
.setSubject(params.getSubject())
.setContent(params.getBody())
.setProvider(mailProviderSend.getProvider())
.setType(event.getType())
.setReceiver(event.getReceiver())
.setSendTime(LocalDateTime.now())
.setSuccess(true);
//@formatter:on
mailSendRecordRepository.save(record);
} catch (Exception e) {
logger.error("邮件信息发送失败: {}", e.getMessage());
throw new MailMessageSendException("邮件信息发送失败!", e);
}
}
/**
*
*
* @param name {@link String}
* @param templateContent {@link String}
* @return {@link Template}
* @throws IOException IOException
*/
private Template createTemplate(String name, String templateContent) throws IOException {
try {
Template template = freeMarkerConfiguration.getTemplate(name, UTF_8);
if (template != null) {
if (template.toString().trim().equals(templateContent.trim())) {
return template;
}
}
} catch (Exception ignored) {
}
// 以下操作不是线程安全,要加上同步
synchronized (this) {
// 获取模板加载器
TemplateLoader templateLoader = freeMarkerConfiguration.getTemplateLoader();
if (templateLoader instanceof StringTemplateLoader) {
// 如果加载器已经是字符串加载器则在原来的加载器上put一个新的模板
((StringTemplateLoader) templateLoader).putTemplate(name, templateContent);
freeMarkerConfiguration.setTemplateLoader(templateLoader);
} else {
// 如果原来的模板加载器不是字符串的(默认是文件加载器),则新建
StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();
stringTemplateLoader.putTemplate(name, templateContent);
freeMarkerConfiguration.setTemplateLoader(stringTemplateLoader);
}
// 这里要清一下缓存,不然下面可能会获取不到模板
freeMarkerConfiguration.clearTemplateCache();
return freeMarkerConfiguration.getTemplate(name, UTF_8);
}
}
/**
* MailSend
*/
private final MailProviderSend mailProviderSend;
/**
* MailSendRecordRepository
*/
private final MailSendRecordRepository mailSendRecordRepository;
/**
* redis
*/
private final RedisTemplate<Object, Object> redisTemplate;
private final Configuration freeMarkerConfiguration;
public MailMsgEventListener(MailProviderSend mailProviderSend,
MailSendRecordRepository mailSendRecordRepository,
RedisTemplate<Object, Object> redisTemplate) {
this.mailProviderSend = mailProviderSend;
this.mailSendRecordRepository = mailSendRecordRepository;
this.redisTemplate = redisTemplate;
freeMarkerConfiguration = new Configuration(Configuration.VERSION_2_3_31);
//设置模板加载文件夹
freeMarkerConfiguration.setClassForTemplateLoading(this.getClass(), "/mail/content");
}
}

View File

@ -23,15 +23,14 @@ import java.util.Map;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.ObjectMapper;
import cn.topiam.employee.common.enums.MailType;
import cn.topiam.employee.common.enums.MessageCategory;
import cn.topiam.employee.common.message.enums.MessageType;
import cn.topiam.employee.common.message.mail.MailProviderConfig;
import cn.topiam.employee.core.mq.NoticeMessagePublisher;
import cn.topiam.employee.support.context.ApplicationContextHelp;
import cn.topiam.employee.support.exception.TopIamException;
@ -53,10 +52,11 @@ import static cn.topiam.employee.support.constant.EiamConstants.DEFAULT_DATE_TIM
@Slf4j
@AllArgsConstructor
public class MailMsgEventPublish {
/**
* NoticeMessagePublisher
* ApplicationEventPublisher
*/
private final NoticeMessagePublisher noticeMessageProducer;
private final ApplicationEventPublisher applicationEventPublisher;
/**
*
@ -101,7 +101,7 @@ public class MailMsgEventPublish {
parameter.put(USER_EMAIL, receiver);
// publish event
ObjectMapper objectMapper = ApplicationContextHelp.getBean(ObjectMapper.class);
noticeMessageProducer.sendNotice(MessageType.MAIL,
objectMapper.writeValueAsString(new MailMessage(type, receiver, parameter)));
applicationEventPublisher.publishEvent(new MailMsgEvent(type, receiver, parameter));
}
}

View File

@ -15,30 +15,36 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.core.dynamic;
package cn.topiam.employee.core.message.sms;
import cn.topiam.employee.support.autoconfiguration.SupportProperties;
import java.util.Map;
import org.springframework.context.ApplicationEvent;
import cn.topiam.employee.common.enums.SmsType;
import lombok.Getter;
/**
* UserIndexName
*
*
* @author TopIAM
* Created by support@topiam.cn on 2022/11/3 23:31
* Created by support@topiam.cn on 2021/9/25 21:07
*/
public class UserIndexName {
private final SupportProperties supportProperties;
public UserIndexName(SupportProperties supportProperties) {
this.supportProperties = supportProperties;
}
@Getter
public class SmsMsgEvent extends ApplicationEvent {
/**
*
*
* @return {@link String}
*
*/
public String getIndexName() {
return supportProperties.getUser().getIndexPrefix();
private final SmsType type;
/**
*
*/
private final Map<String, String> parameter;
public SmsMsgEvent(SmsType type, Map<String, String> parameter) {
super(parameter);
this.type = type;
this.parameter = parameter;
}
}

View File

@ -0,0 +1,118 @@
/*
* eiam-core - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.core.message.sms;
import java.time.LocalDateTime;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationListener;
import org.springframework.lang.NonNull;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson2.JSON;
import cn.topiam.employee.common.entity.message.SmsSendRecordEntity;
import cn.topiam.employee.common.enums.MessageCategory;
import cn.topiam.employee.common.exception.SmsMessageSendException;
import cn.topiam.employee.common.message.sms.SendSmsRequest;
import cn.topiam.employee.common.message.sms.SmsProviderSend;
import cn.topiam.employee.common.message.sms.SmsResponse;
import cn.topiam.employee.common.repository.message.SmsSendRecordRepository;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2021/9/25 21:07
*/
@Async
@Component
public class SmsMsgEventListener implements ApplicationListener<SmsMsgEvent> {
/**
* Logger
*/
private final Logger logger = LoggerFactory.getLogger(SmsMsgEventListener.class);
/**
*
*
* @param event {@link SmsMsgEvent}
*/
@Override
public void onApplicationEvent(@NonNull SmsMsgEvent event) {
SendSmsRequest smsParam = new SendSmsRequest();
try {
//@formatter:off
// 手机号
smsParam.setPhone(event.getParameter().get(SmsMsgEventPublish.PHONE));
// 模版编码
smsParam.setTemplate(event.getParameter().get(SmsMsgEventPublish.TEMPLATE_CODE));
// Content 记录参数值
String content = event.getParameter().get(SmsMsgEventPublish.CONTENT);
// 移除手机号模版编码和Content
event.getParameter().remove(SmsMsgEventPublish.PHONE);
event.getParameter().remove(SmsMsgEventPublish.TEMPLATE_CODE);
event.getParameter().remove(SmsMsgEventPublish.CONTENT);
// 短信模版参数
smsParam.setParameters(event.getParameter());
//@formatter:on
SmsResponse send = smsProviderSend.send(smsParam);
// 保存发送记录
if (!Objects.isNull(send)) {
//@formatter:off
SmsSendRecordEntity record = new SmsSendRecordEntity()
.setContent(content)
.setResult(send.getMessage())
.setSuccess(send.getSuccess())
.setSendTime(LocalDateTime.now())
.setProvider(send.getProvider())
.setCategory(MessageCategory.CODE)
.setType(event.getType())
.setPhone(smsParam.getPhone());
record.setRemark(JSON.toJSONString(send));
if (!send.getSuccess()) {
logger.error("发送短信失败: params: {}, response: {}", smsParam, send);
}
//@formatter:on
smsSendLogRepository.save(record);
} else {
logger.error("发送短信失败,返回值为空: params: {}, ", smsParam);
}
} catch (Exception e) {
logger.error("发送短信消息异常 params:{}, error: {}", smsParam, e.getMessage());
throw new SmsMessageSendException(e);
}
}
/**
* SmsSend
*/
private final SmsProviderSend smsProviderSend;
private final SmsSendRecordRepository smsSendLogRepository;
public SmsMsgEventListener(SmsProviderSend smsProviderSend,
SmsSendRecordRepository smsSendLogRepository) {
this.smsProviderSend = smsProviderSend;
this.smsSendLogRepository = smsSendLogRepository;
}
}

View File

@ -23,18 +23,15 @@ import java.util.Map;
import java.util.Optional;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import cn.topiam.employee.common.entity.setting.config.SmsConfig;
import cn.topiam.employee.common.enums.MessageCategory;
import cn.topiam.employee.common.enums.SmsType;
import cn.topiam.employee.common.message.enums.MessageType;
import cn.topiam.employee.core.mq.NoticeMessagePublisher;
import cn.topiam.employee.support.context.ApplicationContextHelp;
import cn.topiam.employee.support.exception.TopIamException;
import lombok.AllArgsConstructor;
@ -55,18 +52,13 @@ import static cn.topiam.employee.core.message.MsgVariable.VERIFY_CODE;
@Slf4j
@AllArgsConstructor
public class SmsMsgEventPublish {
public static final String TEMPLATE_CODE = "template_code";
public static final String TEMPLATE_CODE = "template_code";
public static final String CONTENT = "content";
public static final String CONTENT = "content";
/**
* NoticeMessagePublisher
*/
private final NoticeMessagePublisher noticeMessageProducer;
public static final String PHONE = "phone";
public static final String PHONE = "phone";
public static final String USERNAME = "username";
public static final String USERNAME = "username";
/**
*
@ -110,9 +102,11 @@ public class SmsMsgEventPublish {
parameter.put(TEMPLATE_CODE, templateConfig.getCode());
parameter.put(CONTENT, JSON.toJSONString(parameter));
// publish event
ObjectMapper objectMapper = ApplicationContextHelp.getBean(ObjectMapper.class);
noticeMessageProducer.sendNotice(MessageType.SMS,
objectMapper.writeValueAsString(new SmsMessage(type, parameter)));
applicationEventPublisher.publishEvent(new SmsMsgEvent(type, parameter));
}
/**
* ApplicationEventPublisher
*/
private final ApplicationEventPublisher applicationEventPublisher;
}

View File

@ -1,58 +0,0 @@
/*
* eiam-core - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.core.mq;
import java.io.IOException;
import java.util.Map;
import org.springframework.amqp.core.Message;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import com.rabbitmq.client.Channel;
import cn.topiam.employee.support.trace.TraceUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import static cn.topiam.employee.support.trace.TraceAspect.TRACE_ID;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2023/5/30 23:12
*/
@Slf4j
@AllArgsConstructor
public abstract class AbstractMessageListener {
/**
*
*
* @param message {@link Message}
* @param channel {@link Channel}
* @param body {@link String}
* @param headers {@link Map}
*/
public void onMessage(Message message, Channel channel, @Payload String body,
@Headers Map<String, Object> headers) throws IOException {
// 设置TraceId
TraceUtils.put(String.valueOf(headers.get(TRACE_ID)));
}
}

View File

@ -1,97 +0,0 @@
/*
* eiam-core - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.core.mq;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.rabbit.AsyncRabbitTemplate;
import org.springframework.amqp.rabbit.RabbitConverterFuture;
import org.springframework.messaging.support.MessageBuilder;
import cn.topiam.employee.support.constant.EiamConstants;
import cn.topiam.employee.support.trace.TraceUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import static cn.topiam.employee.support.trace.TraceAspect.TRACE_ID;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2023/6/15 23:12
*/
@Slf4j
@RequiredArgsConstructor
public abstract class AbstractMessagePublisher {
/**
*
*/
public final static String USER = "user";
/**
*
*/
public final static String USER_SAVE = USER + EiamConstants.POINT + "save";
/**
*
*/
public final static String USER_DELETE = USER + EiamConstants.POINT + "delete";
/**
* /
*/
public final static String NOTICE = "notice";
/**
*
*/
public final static String NOTICE_SMS = NOTICE + EiamConstants.POINT + "sms";
/**
*
*/
public final static String NOTICE_MAIL = NOTICE + EiamConstants.POINT + "mail";
/**
* AsyncRabbitTemplate
*/
private final AsyncRabbitTemplate asyncRabbitTemplate;
/**
* AmqpAdmin
*/
protected final AmqpAdmin amqpAdmin;
/**
* RocketMQ
*
* @param exchange {@link String}
* @param routingKey {@link String}
* @param message {@link String}
*/
public RabbitConverterFuture<Object> sendMessage(String exchange, String routingKey,
String message) {
// 获取traceId放入消息keys属性中
String traceId = TraceUtils.get();
RabbitConverterFuture<Object> future = asyncRabbitTemplate.convertSendAndReceive(exchange,
routingKey, MessageBuilder.withPayload(message).setHeader(TRACE_ID, traceId).build());
return future;
}
}

View File

@ -1,338 +0,0 @@
/*
* eiam-core - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.core.mq;
import java.io.IOException;
import java.io.StringWriter;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Objects;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.lang.NonNull;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson2.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.rabbitmq.client.Channel;
import cn.topiam.employee.common.entity.message.MailSendRecordEntity;
import cn.topiam.employee.common.entity.message.SmsSendRecordEntity;
import cn.topiam.employee.common.entity.setting.MailTemplateEntity;
import cn.topiam.employee.common.enums.MailType;
import cn.topiam.employee.common.enums.MessageCategory;
import cn.topiam.employee.common.exception.MailMessageSendException;
import cn.topiam.employee.common.exception.SmsMessageSendException;
import cn.topiam.employee.common.message.mail.MailNoneProviderSend;
import cn.topiam.employee.common.message.mail.MailProviderSend;
import cn.topiam.employee.common.message.mail.SendMailRequest;
import cn.topiam.employee.common.message.sms.SendSmsRequest;
import cn.topiam.employee.common.message.sms.SmsNoneProviderSend;
import cn.topiam.employee.common.message.sms.SmsProviderSend;
import cn.topiam.employee.common.message.sms.SmsResponse;
import cn.topiam.employee.common.repository.message.MailSendRecordRepository;
import cn.topiam.employee.common.repository.message.SmsSendRecordRepository;
import cn.topiam.employee.core.message.MsgVariable;
import cn.topiam.employee.core.message.mail.MailMessage;
import cn.topiam.employee.core.message.mail.MailUtils;
import cn.topiam.employee.core.message.sms.SmsMessage;
import cn.topiam.employee.core.message.sms.SmsMsgEventPublish;
import cn.topiam.employee.core.setting.constant.MessageSettingConstants;
import cn.topiam.employee.support.context.ApplicationContextHelp;
import lombok.extern.slf4j.Slf4j;
import freemarker.cache.StringTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import static org.apache.commons.codec.CharEncoding.UTF_8;
import static org.springframework.web.util.HtmlUtils.htmlUnescape;
import static cn.topiam.employee.core.mq.AbstractMessagePublisher.NOTICE_MAIL;
import static cn.topiam.employee.core.mq.AbstractMessagePublisher.NOTICE_SMS;
import static cn.topiam.employee.support.constant.EiamConstants.COLON;
/**
* /
*
* @author TopIAM
* Created by support@topiam.cn on 2023/5/30 23:12
*/
@Slf4j
@Component
public class NoticeMessageListener extends AbstractMessageListener {
/**
*
*
* @param message {@link Message}
* @param channel {@link Channel}
* @param body {@link String}
* @param headers {@link Map}
*/
@Override
@RabbitListener(queues = { NOTICE_SMS, NOTICE_MAIL }, ackMode = "MANUAL")
@RabbitHandler()
public void onMessage(Message message, Channel channel, @Payload String body,
@Headers Map<String, Object> headers) throws IOException {
super.onMessage(message, channel, body, headers);
log.info("异步接收ES用户信息入参: [{}]", message);
sendNotice(message, channel, body);
}
/**
*
*
* @param message {@link Message}
* @param channel {@link Channel}
* @param body {@link String}
*/
private String sendNotice(Message message, Channel channel, String body) throws IOException {
try {
String queueName = message.getMessageProperties().getConsumerQueue();
if (Objects.isNull(body)) {
log.warn("接收短信/邮件通知消息内容为空:[{}]", message.getMessageProperties().getDeliveryTag());
return "接收短信/邮件通知消息内容为空";
}
log.info("接收通知消息:[{}]", body);
ObjectMapper objectMapper = ApplicationContextHelp.getBean(ObjectMapper.class);
if (queueName.equals(NOTICE_SMS)) {
sendSms(objectMapper.readValue(body, SmsMessage.class));
} else if (queueName.equals(NOTICE_MAIL)) {
sendMail(objectMapper.readValue(body, MailMessage.class));
}
log.info("处理发送通知完成:[{}]", message.getMessageProperties().getDeliveryTag());
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
return "处理发送通知成功";
} catch (Exception e) {
log.error("处理发送通知消息出现异常: MessageProperties: [{}], message:[{}]",
message.getMessageProperties(), body, e);
channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
return "处理发送通知失败";
}
}
/**
*
*
* @param smsMessage {@link SmsMessage}
*/
private void sendSms(@NonNull SmsMessage smsMessage) {
if (smsProviderSend instanceof SmsNoneProviderSend) {
throw new SmsMessageSendException("暂未配置短信服务");
}
SendSmsRequest smsParam = new SendSmsRequest();
try {
//@formatter:off
// 手机号
smsParam.setPhone(smsMessage.getParameter().get(SmsMsgEventPublish.PHONE));
// 模版编码
smsParam.setTemplate(smsMessage.getParameter().get(SmsMsgEventPublish.TEMPLATE_CODE));
// Content 记录参数值
String content = smsMessage.getParameter().get(SmsMsgEventPublish.CONTENT);
// 移除手机号模版编码和Content
smsMessage.getParameter().remove(SmsMsgEventPublish.PHONE);
smsMessage.getParameter().remove(SmsMsgEventPublish.TEMPLATE_CODE);
smsMessage.getParameter().remove(SmsMsgEventPublish.CONTENT);
// 短信模版参数
smsParam.setParameters(smsMessage.getParameter());
//@formatter:on
SmsResponse send = smsProviderSend.send(smsParam);
// 保存发送记录
if (!Objects.isNull(send)) {
//@formatter:off
SmsSendRecordEntity record = new SmsSendRecordEntity()
.setContent(content)
.setResult(send.getMessage())
.setSuccess(send.getSuccess())
.setSendTime(LocalDateTime.now())
.setProvider(send.getProvider())
.setCategory(MessageCategory.CODE)
.setType(smsMessage.getType())
.setPhone(smsParam.getPhone());
record.setRemark(JSON.toJSONString(send));
if (!send.getSuccess()) {
log.error("发送短信失败: params: {}, response: {}", smsParam, send);
}
//@formatter:on
smsSendLogRepository.save(record);
} else {
log.error("发送短信失败,返回值为空: params: {}, ", smsParam);
}
} catch (Exception e) {
log.error("发送短信消息异常 params:{}, error: {}", smsParam, e.getMessage());
throw new SmsMessageSendException(e);
}
}
/**
*
*
* @param mailMessage {@link MailMessage}
*/
private void sendMail(@NonNull MailMessage mailMessage) {
if (mailProviderSend instanceof MailNoneProviderSend) {
throw new MailMessageSendException("暂未配置邮件服务");
}
// 邮件通知类型
MailType type = mailMessage.getType();
String content = htmlUnescape(MailUtils.readEmailContent(type.getContent()));
String subject = type.getSubject();
String sender = type.getSender();
try {
Map<String, Object> parameter = mailMessage.getParameter();
// 判断是否存在自定义
MailTemplateEntity templateEntity = (MailTemplateEntity) redisTemplate.opsForValue()
.get(MessageSettingConstants.SETTING_EMAIL_TEMPLATE_CACHE_NAME + COLON
+ type.getCode());
if (!Objects.isNull(templateEntity)) {
content = htmlUnescape(templateEntity.getContent());
subject = templateEntity.getSubject();
sender = templateEntity.getSender();
}
// 主题
StringWriter themeStringWriter = new StringWriter();
Template themeTpl = createTemplate(type.getCode() + "-theme", subject);
themeTpl.process(parameter, themeStringWriter);
// 测试邮件
if (parameter.containsKey(MsgVariable.TEST)) {
String test = parameter.get(MsgVariable.TEST).toString();
themeStringWriter.append(test);
}
// 内容
StringWriter contentStringWriter = new StringWriter();
Template contentTpl = createTemplate(type.getCode() + "-content", content);
contentTpl.process(parameter, contentStringWriter);
// 发送邮件
//@formatter:off
SendMailRequest params = new SendMailRequest()
// 发送人
.setSender(sender)
// 接收人
.setReceiver(mailMessage.getReceiver())
// 主题
.setSubject(themeStringWriter.toString())
// 内容
.setBody(contentStringWriter.toString());
//@formatter:on
// 发送邮件
mailProviderSend.sendMailHtml(params);
// 保存发送记录
MailSendRecordEntity record = new MailSendRecordEntity();
//@formatter:off
record.setSender(mailMessage.getReceiver())
.setSubject(params.getSubject())
.setContent(params.getBody())
.setProvider(mailProviderSend.getProvider())
.setType(mailMessage.getType())
.setReceiver(mailMessage.getReceiver())
.setSendTime(LocalDateTime.now())
.setSuccess(true);
//@formatter:on
mailSendRecordRepository.save(record);
} catch (Exception e) {
log.error("邮件信息发送失败: {}", e.getMessage());
throw new MailMessageSendException(e);
}
}
/**
*
*
* @param name {@link String}
* @param templateContent {@link String}
* @return {@link Template}
* @throws IOException IOException
*/
private Template createTemplate(String name, String templateContent) throws IOException {
Template template = freeMarkerConfiguration.getTemplate(name, null, null, UTF_8, true,
true);
if (template != null) {
return template;
}
// 以下操作不是线程安全,要加上同步
synchronized (this) {
// 获取模板加载器
TemplateLoader templateLoader = freeMarkerConfiguration.getTemplateLoader();
if (templateLoader instanceof StringTemplateLoader) {
// 如果加载器已经是字符串加载器则在原来的加载器上put一个新的模板
((StringTemplateLoader) templateLoader).putTemplate(name, templateContent);
freeMarkerConfiguration.setTemplateLoader(templateLoader);
} else {
// 如果原来的模板加载器不是字符串的(默认是文件加载器),则新建
StringTemplateLoader stringTemplateLoader = new StringTemplateLoader();
stringTemplateLoader.putTemplate(name, templateContent);
freeMarkerConfiguration.setTemplateLoader(stringTemplateLoader);
}
// 这里要清一下缓存,不然下面可能会获取不到模板
freeMarkerConfiguration.clearTemplateCache();
return freeMarkerConfiguration.getTemplate(name, UTF_8);
}
}
/**
* MailSend
*/
private final MailProviderSend mailProviderSend;
/**
* MailSendRecordRepository
*/
private final MailSendRecordRepository mailSendRecordRepository;
/**
* redis
*/
private final RedisTemplate<Object, Object> redisTemplate;
/**
* Configuration
*/
private final Configuration freeMarkerConfiguration;
/**
* SmsSend
*/
private final SmsProviderSend smsProviderSend;
/**
* SmsSendRecordRepository
*/
private final SmsSendRecordRepository smsSendLogRepository;
public NoticeMessageListener(MailProviderSend mailProviderSend,
MailSendRecordRepository mailSendRecordRepository,
RedisTemplate<Object, Object> redisTemplate,
SmsProviderSend smsProviderSend,
SmsSendRecordRepository smsSendLogRepository) {
this.mailProviderSend = mailProviderSend;
this.mailSendRecordRepository = mailSendRecordRepository;
this.redisTemplate = redisTemplate;
this.smsProviderSend = smsProviderSend;
this.smsSendLogRepository = smsSendLogRepository;
this.freeMarkerConfiguration = new Configuration(Configuration.VERSION_2_3_31);
//设置模板加载文件夹
this.freeMarkerConfiguration.setClassForTemplateLoading(this.getClass(), "/mail/content");
}
}

View File

@ -1,87 +0,0 @@
/*
* eiam-core - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.core.mq;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.AsyncRabbitTemplate;
import org.springframework.amqp.rabbit.RabbitConverterFuture;
import org.springframework.stereotype.Component;
import cn.topiam.employee.common.message.enums.MessageType;
import cn.topiam.employee.support.constant.EiamConstants;
import lombok.extern.slf4j.Slf4j;
import jakarta.annotation.PostConstruct;
/**
* MQ
*
* @author TopIAM
* Created by support@topiam.cn on 2023/6/15 23:12
*/
@Slf4j
@Component
public class NoticeMessagePublisher extends AbstractMessagePublisher {
@PostConstruct
public void init() {
TopicExchange topicExchange = new TopicExchange(NOTICE);
Queue smsQueue = new Queue(NOTICE_SMS, true);
Queue mailQueue = new Queue(NOTICE_MAIL, true);
amqpAdmin.declareExchange(topicExchange);
amqpAdmin.declareQueue(smsQueue);
amqpAdmin.declareQueue(mailQueue);
amqpAdmin.declareBinding(BindingBuilder.bind(smsQueue).to(topicExchange).with(NOTICE_SMS));
amqpAdmin
.declareBinding(BindingBuilder.bind(mailQueue).to(topicExchange).with(NOTICE_MAIL));
}
/**
*
*
* @param asyncRabbitTemplate {@link AsyncRabbitTemplate}
* @param amqpAdmin {@link AmqpAdmin}
*/
public NoticeMessagePublisher(AsyncRabbitTemplate asyncRabbitTemplate, AmqpAdmin amqpAdmin) {
super(asyncRabbitTemplate, amqpAdmin);
}
/**
*
*
* @param type {@link MessageType}
* @param message {@link String}
*/
public void sendNotice(MessageType type, String message) {
log.info("发送[{}]通知消息, message:[{}]", type.getDesc(), message);
String routingKey = NOTICE + EiamConstants.POINT + type.getCode().toLowerCase();
RabbitConverterFuture<Object> future = sendMessage(NOTICE, routingKey, message);
future.whenComplete((result, ex) -> {
if (ex == null) {
log.info("发送[{}]通知消息成功, message:[{}],处理结果为:[{}]", type.getDesc(), message, result);
} else {
log.info("发送[{}]通知消息异常message:[{}],失败原因:[{}], ", type.getDesc(), message,
ex.getMessage(), ex);
}
});
}
}

View File

@ -1,202 +0,0 @@
/*
* eiam-core - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.core.mq;
import java.io.IOException;
import java.util.*;
import org.jetbrains.annotations.NotNull;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import com.rabbitmq.client.Channel;
import cn.topiam.employee.common.entity.account.UserElasticSearchEntity;
import cn.topiam.employee.common.entity.account.po.UserEsPO;
import cn.topiam.employee.common.repository.account.OrganizationRepository;
import cn.topiam.employee.common.repository.account.UserElasticSearchRepository;
import cn.topiam.employee.common.repository.account.UserRepository;
import lombok.extern.slf4j.Slf4j;
import static cn.topiam.employee.core.mq.AbstractMessagePublisher.USER_DELETE;
import static cn.topiam.employee.core.mq.AbstractMessagePublisher.USER_SAVE;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2023/5/30 23:12
*/
@Slf4j
@Component
public class UserMessageListener extends AbstractMessageListener {
/**
*
*
* @param message {@link Message}
* @param channel {@link Channel}
* @param body {@link String}
* @param headers {@link Map}
*/
@Override
@RabbitListener(queues = { USER_SAVE, USER_DELETE }, ackMode = "MANUAL")
@RabbitHandler()
public void onMessage(Message message, Channel channel, @Payload String body,
@Headers Map<String, Object> headers) throws IOException {
super.onMessage(message, channel, body, headers);
log.info("异步接收ES用户信息入参: [{}]", message);
syncUser(message, channel, body);
}
/**
*
*
* @param message {@link Message}
* @param channel {@link Channel}
* @param body {@link String}
*/
private void syncUser(Message message, Channel channel, String body) {
try {
// 处理消息逻辑
String queueName = message.getMessageProperties().getConsumerQueue();
if (!StringUtils.hasText(body)) {
log.warn("接收用户消息内容为空:[{}]", message.getMessageProperties().getDeliveryTag());
return;
}
log.info("接收用户消息:[{}]", body);
List<String> idList = Arrays.asList(body.split(","));
if (queueName.equals(USER_SAVE)) {
List<UserEsPO> userList = userRepository.getUserList(idList);
List<UserElasticSearchEntity> userElasticSearchEntity = getUserElasticSearchEntity(
userList, organizationRepository);
userElasticSearchRepository.saveAll(userElasticSearchEntity);
} else if (queueName.equals(USER_DELETE)) {
userElasticSearchRepository.deleteAllById(idList);
}
log.info("同步用户数据成功:[{}]", message.getMessageProperties().getDeliveryTag());
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
log.error("处理用户数据失败出现异常: MessageProperties: [{}], 用户ID:[{}], Error:[{}]",
message.getMessageProperties(), body, e.getMessage(), e);
try {
channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
} catch (IOException exception) {
log.error("接收用户消息退回出现异常: MessageProperties: [{}], 用户ID:[{}], Error:[{}]",
message.getMessageProperties(), body, e.getMessage(), e);
}
}
}
/**
* es
*
* @param userList {@link List}
* @param organizationRepository {@link OrganizationRepository}
* @return {@link List}
*/
@NotNull
public static List<UserElasticSearchEntity> getUserElasticSearchEntity(List<UserEsPO> userList,
OrganizationRepository organizationRepository) {
List<UserElasticSearchEntity> userElasticSearchEntityList = new ArrayList<>();
userList.forEach(user -> {
UserElasticSearchEntity entity = UserElasticSearchEntity.builder().build();
entity.setId(user.getId().toString());
entity.setUsername(user.getUsername());
entity.setEmail(user.getEmail());
entity.setPhone(user.getPhone());
entity.setPhoneAreaCode(user.getPhoneAreaCode());
entity.setFullName(user.getFullName());
entity.setNickName(user.getNickName());
entity.setAvatar(user.getAvatar());
if (Objects.nonNull(user.getStatus())) {
entity.setStatus(user.getStatus().getCode());
}
if (Objects.nonNull(user.getDataOrigin())) {
entity.setDataOrigin(user.getDataOrigin().getCode());
}
entity.setIdentitySourceId(user.getIdentitySourceId());
entity.setEmailVerified(user.getEmailVerified());
entity.setPhoneVerified(user.getPhoneVerified());
entity.setAuthTotal(user.getAuthTotal());
entity.setLastAuthIp(user.getLastAuthIp());
if (Objects.nonNull(user.getLastUpdatePasswordTime())) {
entity.setLastUpdatePasswordTime(user.getLastUpdatePasswordTime());
}
if (Objects.nonNull(user.getLastAuthTime())) {
entity.setLastAuthTime(user.getLastAuthTime());
}
entity.setExpand(user.getExpand());
entity.setExternalId(user.getExternalId());
if (Objects.nonNull(user.getExpireDate())) {
entity.setExpireDate(user.getExpireDate());
}
// 用户详情
entity.setIdType(user.getIdType());
entity.setIdCard(user.getIdCard());
entity.setWebsite(user.getWebsite());
entity.setAddress(user.getAddress());
// 组织列表
if (!CollectionUtils.isEmpty(user.getOrganizationIds())) {
entity.setOrganizations(organizationRepository
.getOrganizationList(new ArrayList<>(user.getOrganizationIds())));
}
// 用户组
List<UserElasticSearchEntity.UserGroup> userGroups = new ArrayList<>();
if (!CollectionUtils.isEmpty(user.getUserGroups())) {
userGroups.addAll(user.getUserGroups().entrySet().stream()
.map(group -> new UserElasticSearchEntity.UserGroup(group.getKey(),
group.getValue()))
.toList());
}
entity.setUserGroups(userGroups);
userElasticSearchEntityList.add(entity);
});
return userElasticSearchEntityList;
}
/**
* UserElasticSearchRepository
*/
private final UserElasticSearchRepository userElasticSearchRepository;
/**
* UserRepository
*/
private final UserRepository userRepository;
/**
* OrganizationRepository
*/
private final OrganizationRepository organizationRepository;
public UserMessageListener(UserElasticSearchRepository userElasticSearchRepository,
UserRepository userRepository,
OrganizationRepository organizationRepository) {
this.userElasticSearchRepository = userElasticSearchRepository;
this.userRepository = userRepository;
this.organizationRepository = organizationRepository;
}
}

View File

@ -1,85 +0,0 @@
/*
* eiam-core - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.core.mq;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.AsyncRabbitTemplate;
import org.springframework.amqp.rabbit.RabbitConverterFuture;
import org.springframework.stereotype.Component;
import cn.topiam.employee.support.constant.EiamConstants;
import lombok.extern.slf4j.Slf4j;
import jakarta.annotation.PostConstruct;
/**
* MQ
*
* @author TopIAM
* Created by support@topiam.cn on 2023/5/30 23:12
*/
@Slf4j
@Component
public class UserMessagePublisher extends AbstractMessagePublisher {
@PostConstruct
public void init() {
TopicExchange topicExchange = new TopicExchange(USER);
Queue saveQueue = new Queue(USER_SAVE, true);
Queue deleteQueue = new Queue(USER_DELETE, true);
amqpAdmin.declareExchange(topicExchange);
amqpAdmin.declareQueue(saveQueue);
amqpAdmin.declareQueue(deleteQueue);
amqpAdmin.declareBinding(BindingBuilder.bind(saveQueue).to(topicExchange).with(USER_SAVE));
amqpAdmin
.declareBinding(BindingBuilder.bind(deleteQueue).to(topicExchange).with(USER_DELETE));
}
/**
*
*
* @param asyncRabbitTemplate {@link AsyncRabbitTemplate}
* @param amqpAdmin {@link AmqpAdmin}
*/
public UserMessagePublisher(AsyncRabbitTemplate asyncRabbitTemplate, AmqpAdmin amqpAdmin) {
super(asyncRabbitTemplate, amqpAdmin);
}
/**
*
*
* @param tag {@link UserMessageTag} SAVE/DELETE
* @param userIds {@link String} id
*/
public void sendUserChangeMessage(UserMessageTag tag, String userIds) {
log.info("发送[{}]用户消息, 用户ID:[{}]", tag.name(), userIds);
String routingKey = USER + EiamConstants.POINT + tag.name().toLowerCase();
RabbitConverterFuture<Object> future = sendMessage(USER, routingKey, userIds);
future.whenComplete((result, ex) -> {
if (ex == null) {
log.info("发送[{}]用户消息成功, 用户ID:[{}],处理结果为:[{}]", tag.name(), userIds, result);
} else {
log.info("发送[{}]用户消息异常用户ID:[{}],失败原因:[{}] ", tag.name(), userIds, ex);
}
});
}
}

View File

@ -1,35 +0,0 @@
/*
* eiam-core - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.core.mq;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2023/5/30 23:12
*/
public enum UserMessageTag {
/**
*
*/
DELETE,
/**
*
*/
SAVE
}

View File

@ -19,36 +19,23 @@ package cn.topiam.employee.core.security.password.task.impl;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.scheduling.annotation.Scheduled;
import com.google.common.collect.Lists;
import cn.topiam.employee.common.entity.account.UserElasticSearchEntity;
import cn.topiam.employee.common.entity.account.UserEntity;
import cn.topiam.employee.common.entity.setting.SettingEntity;
import cn.topiam.employee.common.enums.UserStatus;
import cn.topiam.employee.common.repository.account.UserRepository;
import cn.topiam.employee.common.repository.setting.SettingRepository;
import cn.topiam.employee.core.mq.UserMessagePublisher;
import cn.topiam.employee.core.mq.UserMessageTag;
import cn.topiam.employee.core.security.password.task.PasswordExpireTask;
import cn.topiam.employee.support.autoconfiguration.SupportProperties;
import cn.topiam.employee.support.lock.Lock;
import cn.topiam.employee.support.trace.Trace;
import lombok.RequiredArgsConstructor;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders;
import co.elastic.clients.elasticsearch._types.query_dsl.TermsQueryField;
import static cn.topiam.employee.core.setting.constant.PasswordPolicySettingConstants.PASSWORD_POLICY_DEFAULT_SETTINGS;
import static cn.topiam.employee.core.setting.constant.PasswordPolicySettingConstants.PASSWORD_POLICY_VALID_DAYS;
@ -66,48 +53,30 @@ public class PasswordExpireLockTask implements PasswordExpireTask {
/**
*
*/
@Lock(throwException = false)
@Trace
@Scheduled(cron = "0 0 0 * * ?")
@Override
public void execute() {
long start = System.currentTimeMillis();
logger.info("密码过期锁定用户任务开始");
int expireDays = getExpireDays();
// 查询非密码过期锁定和过期锁定用户信息
Query query = QueryBuilders.terms(builder -> {
builder.terms(new TermsQueryField.Builder().value(
Lists.newArrayList(FieldValue.of(UserStatus.PASSWORD_EXPIRED_LOCKED.getCode()),
FieldValue.of(UserStatus.EXPIRED_LOCKED.getCode())))
.build());
builder.field("status");
return builder;
});
List<UserElasticSearchEntity> userElasticSearchList = userRepository
.getAllUserElasticSearchEntity(
IndexCoordinates.of(supportProperties.getUser().getIndexPrefix()), query);
List<String> lockedUserIdList = new ArrayList<>();
Iterator<UserElasticSearchEntity> iterator = userElasticSearchList.iterator();
logger.info("密码过期待锁定用户数量为:{}个", userElasticSearchList.size());
while (!userElasticSearchList.isEmpty()) {
UserElasticSearchEntity entity = iterator.next();
//1、根据提醒时间分页查询即将要过期的密码
List<UserEntity> list = userRepository.findPasswordExpireUser(expireDays);
Iterator<UserEntity> iterator = list.iterator();
logger.info("密码过期待锁定用户数量为:{}个", list.size());
while (!list.isEmpty()) {
UserEntity entity = iterator.next();
//获取到期日期
LocalDateTime expiredDate = entity.getLastUpdatePasswordTime().atOffset(ZoneOffset.MAX)
.plusDays(expireDays).toLocalDateTime();
if (LocalDateTime.now().isBefore(expiredDate)) {
lockedUserIdList.add(entity.getId());
userRepository.updateUserStatus(Long.valueOf(entity.getId()),
UserStatus.PASSWORD_EXPIRED_LOCKED);
logger.info("锁定密码过期用户:{}", entity.getUsername());
entity.setStatus(UserStatus.PASSWORD_EXPIRED_LOCKED);
userRepository.save(entity);
logger.info("锁定密码过期用户:{}", entity.getUsername());
iterator.remove();
}
iterator = userElasticSearchList.iterator();
iterator = list.iterator();
}
// 推送es用户消息
userMessagePublisher.sendUserChangeMessage(UserMessageTag.SAVE,
String.join(",", lockedUserIdList));
logger.info("密码过期锁定用户任务结束: 冻结用户数量[{}], 耗时:[{}]s", lockedUserIdList.size(),
(System.currentTimeMillis() - start) / 1000);
logger.info("密码过期锁定用户任务结束");
}
/**
@ -126,21 +95,11 @@ public class PasswordExpireLockTask implements PasswordExpireTask {
/**
*
*/
private final SettingRepository settingRepository;
private final SettingRepository settingRepository;
/**
* UserRepository
*/
private final UserRepository userRepository;
/**
* SupportProperties
*/
private final SupportProperties supportProperties;
/**
* UserMessagePublisher
*/
private final UserMessagePublisher userMessagePublisher;
private final UserRepository userRepository;
}

View File

@ -19,7 +19,6 @@ package cn.topiam.employee.core.security.password.task.impl;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.LinkedHashMap;
@ -29,29 +28,22 @@ import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.elasticsearch.client.elc.Queries;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.scheduling.annotation.Scheduled;
import com.google.common.collect.Maps;
import cn.topiam.employee.common.entity.account.UserElasticSearchEntity;
import cn.topiam.employee.common.entity.account.UserEntity;
import cn.topiam.employee.common.entity.setting.SettingEntity;
import cn.topiam.employee.common.enums.MailType;
import cn.topiam.employee.common.enums.SmsType;
import cn.topiam.employee.common.enums.UserStatus;
import cn.topiam.employee.common.repository.account.UserRepository;
import cn.topiam.employee.common.repository.setting.SettingRepository;
import cn.topiam.employee.core.message.mail.MailMsgEventPublish;
import cn.topiam.employee.core.message.sms.SmsMsgEventPublish;
import cn.topiam.employee.core.security.password.task.PasswordExpireTask;
import cn.topiam.employee.support.autoconfiguration.SupportProperties;
import cn.topiam.employee.support.lock.Lock;
import cn.topiam.employee.support.trace.Trace;
import lombok.RequiredArgsConstructor;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import static cn.topiam.employee.core.message.MsgVariable.EXPIRE_DAYS;
import static cn.topiam.employee.core.setting.constant.PasswordPolicySettingConstants.*;
@ -68,59 +60,48 @@ public class PasswordExpireWarnTask implements PasswordExpireTask {
/**
*
*/
@Lock(throwException = false)
@Trace
@Scheduled(cron = "0 0 0 * * ?")
@Override
public void execute() {
long start = System.currentTimeMillis();
logger.info("密码过期前提醒任务开始");
//密码有效期天数
int expireDays = getExpireDays();
//密码过期前提醒天数
int warnExpireDays = getWarnExpireDays();
//1、根据提醒时间分页查询即将要过期的密码
// 查询已启用用户信息
Query query = Queries.termQueryAsQuery("status", UserStatus.ENABLE.getCode());
List<UserElasticSearchEntity> list = userRepository.getAllUserElasticSearchEntity(
IndexCoordinates.of(supportProperties.getUser().getIndexPrefix()), query);
List<UserEntity> list = userRepository
.findPasswordExpireWarnUser(expireDays - warnExpireDays);
logger.info("待提醒用户为:{}个", list.size());
int reminderNumber = 0;
//2、发送通知提醒
for (UserElasticSearchEntity entity : list) {
// 获取到密码期日期
LocalDate expiredDate = entity.getLastUpdatePasswordTime().atOffset(ZoneOffset.MAX)
.plusDays(expireDays).toLocalDate();
LocalDate warnDate = expiredDate.minusDays(warnExpireDays);
//如果当前日期等于密码到期日期前几天的日期
if (LocalDate.now().equals(warnDate)) {
reminderNumber++;
long expiredDays = Duration.between(LocalDate.now().atTime(LocalTime.MIN),
expiredDate.atTime(LocalTime.MIN)).toDays();
//如果邮箱不为空,发送邮件通知
if (StringUtils.isNotBlank(entity.getEmail())) {
logger.info("开始提醒用户:{}", entity.getUsername());
HashMap<String, Object> map = Maps.newHashMap();
//查询两个时间的间距
map.put(EXPIRE_DAYS, expiredDays);
mailMsgEventPublish.publish(MailType.PASSWORD_SOON_EXPIRED_REMIND,
entity.getEmail(), map);
logger.info("结束提醒用户:{}", entity.getUsername());
}
// 短信通知
if (StringUtils.isNotBlank(entity.getPhone())) {
logger.info("开始提醒用户:{}", entity.getUsername());
LinkedHashMap<String, String> map = Maps.newLinkedHashMap();
// 查询两个时间的间距
map.put(EXPIRE_DAYS, String.valueOf(expiredDays));
smsMsgEventPublish.publish(SmsType.PASSWORD_SOON_EXPIRED_REMIND,
entity.getPhone(), map);
logger.info("结束提醒用户:{}", entity.getUsername());
}
for (UserEntity entity : list) {
//如果邮箱不为空,发送邮件通知
if (StringUtils.isNotBlank(entity.getEmail())) {
logger.info("开始提醒用户:{}", entity.getUsername());
//获取到期日期
LocalDate expiredDate = entity.getLastUpdatePasswordTime().atOffset(ZoneOffset.MAX)
.plusDays(expireDays).toLocalDate();
HashMap<String, Object> map = Maps.newHashMap();
//查询两个时间的间距
map.put(EXPIRE_DAYS, Duration.between(LocalDate.now(), expiredDate).toDays());
mailMsgEventPublish.publish(MailType.PASSWORD_SOON_EXPIRED_REMIND,
entity.getEmail(), map);
logger.info("结束提醒用户:{}", entity.getUsername());
}
// 短信通知
if (StringUtils.isNotBlank(entity.getPhone())) {
logger.info("开始提醒用户:{}", entity.getUsername());
// 获取到期日期
LocalDate expiredDate = entity.getLastUpdatePasswordTime().atOffset(ZoneOffset.MAX)
.plusDays(expireDays).toLocalDate();
LinkedHashMap<String, String> map = Maps.newLinkedHashMap();
// 查询两个时间的间距
map.put(EXPIRE_DAYS,
String.valueOf(Duration.between(LocalDate.now(), expiredDate).toDays()));
smsMsgEventPublish.publish(SmsType.PASSWORD_SOON_EXPIRED_REMIND, entity.getPhone(),
map);
logger.info("结束提醒用户:{}", entity.getUsername());
}
}
logger.info("密码过期前提醒任务结束, 提醒用户数量[{}], 耗时:[{}]s", reminderNumber,
(System.currentTimeMillis() - start) / 1000);
logger.info("密码过期前提醒任务结束");
}
/**
@ -169,9 +150,4 @@ public class PasswordExpireWarnTask implements PasswordExpireTask {
*
*/
private final SmsMsgEventPublish smsMsgEventPublish;
/**
* SupportProperties
*/
private final SupportProperties supportProperties;
}

View File

@ -1,143 +0,0 @@
/*
* eiam-core - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package schema;
import cn.topiam.employee.common.schema.FormSchema;
import cn.topiam.employee.common.schema.field.*;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.concurrent.ConcurrentMap;
/**
* FormSchemaTests
*
* @author TopIAM
* Created by support@topiam.cn on 2023/4/12 21:17
*/
public class FormSchemaTests {
private final Logger logger= LoggerFactory.getLogger(FormSchemaTests.class);
@Test
public void test() throws JsonProcessingException {
//@formatter:off
// Input
Input input = (Input) new Input("输入框")
.addRule(new Field.Rule().required(true).min(2).max(20).type("string"))
.setPlaceholder(Lists.newArrayList("请输入")).setDisabled(false);
//数字输入
NumberInput numberInput = new NumberInput("数字");
//文本区域
TextArea textArea = new TextArea("文本区域");
// Select
ConcurrentMap<String, Object> selectProps = Maps.newConcurrentMap();
selectProps.put("allowClear", "true");
Select select = (Select) new Select("下拉单选",
Lists.newArrayList(
new Option("选项A", "A"),
new Option("选项B", "B"),
new Option("选项C", "C")))
.addRule(new Field.Rule().required(true).message("请选择一个选项"))
.setPlaceholder(Lists.newArrayList("请选择一个选项"))
.addProps(selectProps);
//多选Select
MultiSelect multiSelect = (MultiSelect) new MultiSelect("下拉多选",
Lists.newArrayList(
new Option("选项A", "A"),
new Option("选项B", "B"),
new Option("选项C", "C")))
.addRule(new Field.Rule().required(true).message("请选择一个选项"))
.setPlaceholder(Lists.newArrayList("请选择一个选项"))
.addProps(selectProps);
//Switch
Switch aswitch = new Switch("开关");
//Radio
Radio radio = (Radio) new Radio("单选框",
Lists.newArrayList(
new Option("选项A", "A"),
new Option("选项B", "B"),
new Option("选项C", "C")))
.setRules(Lists.newArrayList(new Field.Rule().required(true).message("请选择一个选项")));
//多选框
Checkboxes checkBoxes = new Checkboxes("多选框", Lists.newArrayList(
new Option("选项A", "A"),
new Option("选项B", "B"),
new Option("选项C", "C")));
//单选框
Checkbox checkBox = new Checkbox("单选框");
//年
Year year = new Year("年");
//季度
Quarter quarter = new Quarter("季度");
//月份
Month month = new Month("月份");
//周选择
Week week = new Week("周");
//日期
Date date = new Date("日期");
//日期时间
DateTime dateTime = new DateTime("日期时间");
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//FormSchema
FormSchema formSchema = new FormSchema();
formSchema.setType("object");
HashMap<String, Object> map = new HashMap<>(16);
map.put("input", input);
map.put("number", numberInput);
map.put("textarea", textArea);
map.put("select", select);
map.put("multiselect", multiSelect);
map.put("radio", radio);
map.put("switch", aswitch);
map.put("checkbox", checkBox);
map.put("checkboxes", checkBoxes);
map.put("year", year);
map.put("month", month);
map.put("quarter", quarter);
map.put("week", week);
map.put("date", date);
map.put("date_time", dateTime);
formSchema.setProperties(map);
String schema = objectMapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(formSchema);
logger.info("JSON Schema: \n{}",schema);
}
}

View File

@ -20,13 +20,6 @@ package cn.topiam.employee;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
import cn.topiam.employee.audit.controller.AuditController;
import cn.topiam.employee.common.exception.handler.GlobalExceptionHandler;
import cn.topiam.employee.common.storage.controller.StorageFileResource;
import cn.topiam.employee.core.configuration.EiamApiConfiguration;
/**
*
@ -35,11 +28,7 @@ import cn.topiam.employee.core.configuration.EiamApiConfiguration;
* Created by support@topiam.cn on 2020/7/9
*/
@ServletComponentScan
@SpringBootApplication(scanBasePackages = { "cn.topiam.employee" })
@ComponentScan(excludeFilters = { @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = { EiamApiConfiguration.class,
StorageFileResource.class,
AuditController.class,
GlobalExceptionHandler.class }) })
@SpringBootApplication
public class EiamOpenApiApplication {
public static void main(String[] args) {
SpringApplication.run(EiamOpenApiApplication.class, args);

Some files were not shown because too many files have changed in this diff Show More