!13 LDAP优化增强

* cursorrules
* fixed: ldap同步的部门记录name记录错误
* 主题色一致
* changelog
* admin接入ldap同步数据统计
* ldap同步数据记录接口合并
* fixed: 已同步被禁用用户的DN更新
* 已经同步的LDAP用户被禁止可以继续更新
* 优化代码
* 新增LDAP同步的详细记录
* 新增LDAP禁止用户的数据量统计
* 优化LDAP拉取数据的重复使用
* 优化LDAP同步
* ldap同步记录
* cursor rules
This commit is contained in:
白书科技
2025-05-19 06:25:34 +00:00
parent b9f600d3bc
commit c206fa4bf2
40 changed files with 2588 additions and 39 deletions

View File

@@ -15,7 +15,11 @@
*/
package xyz.playedu.common.bus;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -26,12 +30,17 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import xyz.playedu.common.domain.Department;
import xyz.playedu.common.domain.LdapDepartment;
import xyz.playedu.common.domain.LdapSyncDepartmentDetail;
import xyz.playedu.common.domain.LdapSyncRecord;
import xyz.playedu.common.domain.LdapSyncUserDetail;
import xyz.playedu.common.domain.LdapUser;
import xyz.playedu.common.domain.User;
import xyz.playedu.common.exception.NotFoundException;
import xyz.playedu.common.service.*;
import xyz.playedu.common.types.LdapConfig;
import xyz.playedu.common.types.config.S3Config;
import xyz.playedu.common.util.HelperUtil;
import xyz.playedu.common.util.S3Util;
import xyz.playedu.common.util.ldap.LdapTransformDepartment;
import xyz.playedu.common.util.ldap.LdapTransformUser;
import xyz.playedu.common.util.ldap.LdapUtil;
@@ -50,16 +59,381 @@ public class LDAPBus {
@Autowired private UserService userService;
@Autowired private LdapSyncRecordService ldapSyncRecordService;
@Autowired private LdapSyncDepartmentDetailService ldapSyncDepartmentDetailService;
@Autowired private LdapSyncUserDetailService ldapSyncUserDetailService;
public boolean enabledLDAP() {
return appConfigService.enabledLdapLogin();
}
public void departmentSync() throws NamingException, NotFoundException {
LdapConfig ldapConfig = appConfigService.ldapConfig();
/** 检查是否有进行中的同步任务 */
public boolean hasSyncInProgress() {
return ldapSyncRecordService.hasSyncInProgress();
}
List<LdapTransformDepartment> ouList =
LdapUtil.departments(ldapConfig, ldapConfig.getBaseDN());
/**
* 执行LDAP同步并记录同步数据
*
* @param adminId 执行同步的管理员ID0为系统自动执行
* @return 同步记录ID
*/
public Integer syncAndRecord(Integer adminId)
throws NamingException, IOException, NotFoundException {
// 检查是否有进行中的同步任务
if (hasSyncInProgress()) {
throw new RuntimeException("有正在进行的LDAP同步任务请稍后再试");
}
// 创建同步记录
LdapSyncRecord record = ldapSyncRecordService.create(adminId);
try {
// 获取LDAP配置
LdapConfig ldapConfig = appConfigService.ldapConfig();
// 查询LDAP数据只查询一次
List<LdapTransformDepartment> departments =
LdapUtil.departments(ldapConfig, ldapConfig.getBaseDN());
List<LdapTransformUser> users = LdapUtil.users(ldapConfig, ldapConfig.getBaseDN());
// 使用查询的数据进行统计
Map<String, Object> result = collectSyncStatistics(departments, users);
// 将同步数据保存到S3
String s3FilePath = saveDataToS3(result, record.getId());
// 收集部门和用户的详细同步信息
List<LdapSyncDepartmentDetail> departmentDetails =
collectDepartmentSyncDetails(record.getId(), departments);
List<LdapSyncUserDetail> userDetails = collectUserSyncDetails(record.getId(), users);
// 使用同样的数据执行实际同步
departmentSync(departments);
userSync(users);
// 保存部门和用户的详细同步信息
ldapSyncDepartmentDetailService.batchCreate(departmentDetails);
ldapSyncUserDetailService.batchCreate(userDetails);
// 更新同步记录
ldapSyncRecordService.updateSyncResult(
record.getId(),
1, // 成功
s3FilePath,
(Integer) result.get("totalDepartmentCount"),
(Integer) result.get("createdDepartmentCount"),
(Integer) result.get("updatedDepartmentCount"),
(Integer) result.get("deletedDepartmentCount"),
(Integer) result.get("totalUserCount"),
(Integer) result.get("createdUserCount"),
(Integer) result.get("updatedUserCount"),
(Integer) result.get("deletedUserCount"),
(Integer) result.get("bannedUserCount"));
return record.getId();
} catch (Exception e) {
// 记录同步失败
ldapSyncRecordService.updateSyncFailed(record.getId(), e.getMessage());
log.error("LDAP同步失败", e);
throw e;
}
}
/**
* 收集部门同步详情
*
* @param recordId 同步记录ID
* @param departments LDAP部门数据
* @return 部门同步详情列表
*/
private List<LdapSyncDepartmentDetail> collectDepartmentSyncDetails(
Integer recordId, List<LdapTransformDepartment> departments) throws NotFoundException {
List<LdapSyncDepartmentDetail> details = new ArrayList<>();
Date now = new Date();
// 读取已经同步的记录
Map<String, LdapDepartment> ldapDepartments =
ldapDepartmentService.all().stream()
.collect(Collectors.toMap(LdapDepartment::getUuid, e -> e));
// 记录新增和更新的部门
for (LdapTransformDepartment dept : departments) {
LdapDepartment existingDept = ldapDepartments.get(dept.getUuid());
LdapSyncDepartmentDetail detail = new LdapSyncDepartmentDetail();
detail.setRecordId(recordId);
detail.setUuid(dept.getUuid());
detail.setDn(dept.getDn());
// 从DN中提取部门名称
String[] parts = dept.getDn().split(",");
String name = parts[parts.length - 1].replace("ou=", "");
detail.setName(name);
detail.setCreatedAt(now);
if (existingDept == null) {
// 新增部门
detail.setAction(1);
} else if (!existingDept.getDn().equals(dept.getDn())) {
// 更新部门
detail.setDepartmentId(existingDept.getDepartmentId());
detail.setAction(2);
} else {
// 无变化
detail.setDepartmentId(existingDept.getDepartmentId());
detail.setAction(4);
}
details.add(detail);
}
// 记录删除的部门
List<String> uuidList = departments.stream().map(LdapTransformDepartment::getUuid).toList();
List<LdapDepartment> deletedDepts = ldapDepartmentService.notChunkByUUIDList(uuidList);
if (deletedDepts != null && !deletedDepts.isEmpty()) {
for (LdapDepartment dept : deletedDepts) {
LdapSyncDepartmentDetail detail = new LdapSyncDepartmentDetail();
detail.setRecordId(recordId);
detail.setDepartmentId(dept.getDepartmentId());
detail.setUuid(dept.getUuid());
detail.setDn(dept.getDn());
// 获取部门名称
Department department = departmentService.findOrFail(dept.getDepartmentId());
detail.setName(department.getName());
detail.setAction(3); // 删除
detail.setCreatedAt(now);
details.add(detail);
}
}
return details;
}
/**
* 收集用户同步详情
*
* @param recordId 同步记录ID
* @param users LDAP用户数据
* @return 用户同步详情列表
*/
private List<LdapSyncUserDetail> collectUserSyncDetails(
Integer recordId, List<LdapTransformUser> users) {
List<LdapSyncUserDetail> details = new ArrayList<>();
Date now = new Date();
// 处理新增和更新的用户
for (LdapTransformUser user : users) {
LdapSyncUserDetail detail = new LdapSyncUserDetail();
detail.setRecordId(recordId);
detail.setUuid(user.getId());
detail.setDn(user.getDn());
detail.setCn(user.getCn());
detail.setUid(user.getUid());
detail.setEmail(user.getEmail());
detail.setOu(String.join(",", user.getOu()));
detail.setCreatedAt(now);
// 查找现有用户
LdapUser existingUser = ldapUserService.findByUUID(user.getId());
// 检查用户是否被禁止
if (user.isBan()) {
// 标记为禁止的用户
detail.setAction(5); // 5-禁止的用户
if (existingUser != null) {
detail.setUserId(existingUser.getUserId());
}
details.add(detail);
continue;
}
if (existingUser == null) {
// 新增用户
detail.setAction(1);
} else {
// 检查是否有变更
boolean hasChanges = false;
if (!user.getCn().equals(existingUser.getCn())) {
hasChanges = true;
}
String newOU = String.join(",", user.getOu());
if (!newOU.equals(existingUser.getOu())) {
hasChanges = true;
}
// 设置用户ID
detail.setUserId(existingUser.getUserId());
if (hasChanges) {
// 更新用户
detail.setAction(2);
} else {
// 无变化
detail.setAction(4);
}
}
details.add(detail);
}
// 处理删除的用户
List<String> uuidList =
users.stream().filter(u -> !u.isBan()).map(LdapTransformUser::getId).toList();
// 获取所有现有的LDAP用户记录
List<LdapUser> allLdapUsers = ldapUserService.list();
// 过滤出不在当前LDAP用户列表中的用户
List<LdapUser> deletedUsers =
allLdapUsers.stream().filter(lu -> !uuidList.contains(lu.getUuid())).toList();
for (LdapUser deletedUser : deletedUsers) {
LdapSyncUserDetail detail = new LdapSyncUserDetail();
detail.setRecordId(recordId);
detail.setUserId(deletedUser.getUserId());
detail.setUuid(deletedUser.getUuid());
detail.setDn(deletedUser.getDn());
detail.setCn(deletedUser.getCn());
detail.setUid(deletedUser.getUid());
detail.setEmail(deletedUser.getEmail());
detail.setOu(deletedUser.getOu());
detail.setAction(3); // 删除
detail.setCreatedAt(now);
details.add(detail);
}
return details;
}
/** 收集同步统计数据 */
private Map<String, Object> collectSyncStatistics(
List<LdapTransformDepartment> departments, List<LdapTransformUser> users) {
Map<String, Object> result = new HashMap<>();
Map<String, Object> syncData = new HashMap<>();
// 部门同步统计
int totalDepartmentCount = 0;
int createdDepartmentCount = 0;
int updatedDepartmentCount = 0;
int deletedDepartmentCount = 0;
// 用户同步统计
int totalUserCount = 0;
int createdUserCount = 0;
int updatedUserCount = 0;
int deletedUserCount = 0;
int bannedUserCount = 0;
// 处理部门数据
if (departments != null && !departments.isEmpty()) {
syncData.put("departments", departments);
totalDepartmentCount = departments.size();
// 读取已经同步的记录
Map<String, LdapDepartment> ldapDepartments =
ldapDepartmentService.all().stream()
.collect(Collectors.toMap(LdapDepartment::getUuid, e -> e));
// 计算新增和更新的部门
for (LdapTransformDepartment dept : departments) {
LdapDepartment existingDept = ldapDepartments.get(dept.getUuid());
if (existingDept == null) {
createdDepartmentCount++;
} else if (!existingDept.getDn().equals(dept.getDn())) {
updatedDepartmentCount++;
}
}
// 计算删除的部门
List<String> uuidList =
departments.stream().map(LdapTransformDepartment::getUuid).toList();
List<LdapDepartment> ldapDepartmentList =
ldapDepartmentService.notChunkByUUIDList(uuidList);
deletedDepartmentCount = ldapDepartmentList != null ? ldapDepartmentList.size() : 0;
}
// 处理用户数据
if (users != null && !users.isEmpty()) {
syncData.put("users", users);
totalUserCount = users.size();
// 计算被禁止的用户数量
bannedUserCount = (int) users.stream().filter(LdapTransformUser::isBan).count();
// 计算新增、更新的用户
for (LdapTransformUser user : users) {
if (user.isBan()) {
continue;
}
LdapUser existingUser = ldapUserService.findByUUID(user.getId());
if (existingUser == null) {
createdUserCount++;
} else {
// 检查用户信息是否有变化
boolean hasChanges = false;
if (!user.getCn().equals(existingUser.getCn())) {
hasChanges = true;
}
String newOU = String.join(",", user.getOu());
if (!newOU.equals(existingUser.getOu())) {
hasChanges = true;
}
if (hasChanges) {
updatedUserCount++;
}
}
}
}
// 将同步结果数据存储到结果对象中
result.put("data", syncData);
result.put("totalDepartmentCount", totalDepartmentCount);
result.put("createdDepartmentCount", createdDepartmentCount);
result.put("updatedDepartmentCount", updatedDepartmentCount);
result.put("deletedDepartmentCount", deletedDepartmentCount);
result.put("totalUserCount", totalUserCount);
result.put("createdUserCount", createdUserCount);
result.put("updatedUserCount", updatedUserCount);
result.put("deletedUserCount", deletedUserCount);
result.put("bannedUserCount", bannedUserCount);
return result;
}
/** 将同步数据保存到S3 */
private String saveDataToS3(Map<String, Object> data, Integer recordId) throws IOException {
// 将数据转换为JSON
ObjectMapper objectMapper = new ObjectMapper();
String jsonData = objectMapper.writeValueAsString(data);
byte[] jsonBytes = jsonData.getBytes(StandardCharsets.UTF_8);
// 生成保存路径
String filename = "ldap_sync_" + recordId + "_" + new Date().getTime() + ".json";
String savePath = "ldap/sync/" + filename;
// 保存到S3
S3Config s3Config = appConfigService.getS3Config();
S3Util s3Util = new S3Util(s3Config);
s3Util.saveBytes(jsonBytes, savePath, "application/json");
return savePath;
}
/**
* 执行部门同步 - 提供现有的LDAP部门数据
*
* @param ouList 已获取的LDAP部门数据
*/
public void departmentSync(List<LdapTransformDepartment> ouList) throws NotFoundException {
if (ouList == null || ouList.isEmpty()) {
return;
}
@@ -203,11 +577,12 @@ public class LDAPBus {
}
}
public void userSync() throws NamingException, IOException {
LdapConfig ldapConfig = appConfigService.ldapConfig();
List<LdapTransformUser> userList = LdapUtil.users(ldapConfig, ldapConfig.getBaseDN());
/**
* 执行用户同步 - 提供现有的LDAP用户数据
*
* @param userList 已获取的LDAP用户数据
*/
public void userSync(List<LdapTransformUser> userList) {
if (userList == null || userList.isEmpty()) {
return;
}
@@ -216,12 +591,18 @@ public class LDAPBus {
for (LdapTransformUser ldapTransformUser : userList) {
if (ldapTransformUser.isBan()) {
log.info(
"LDAP-用户同步-用户被禁止|ctx=[dn:{},uuid={}]",
ldapTransformUser.getDn(),
ldapTransformUser.getId());
continue;
// 检查用户是否已在系统中存在
LdapUser existingLdapUser = ldapUserService.findByUUID(ldapTransformUser.getId());
if (existingLdapUser == null) {
// 对于新的被禁止用户,不同步到系统
log.info(
"LDAP-用户同步-新用户被禁止不同步|ctx=[dn:{},uuid={}]",
ldapTransformUser.getDn(),
ldapTransformUser.getId());
continue;
}
}
singleUserSync(ldapTransformUser, defaultAvatar);
}
}
@@ -326,6 +707,15 @@ public class LDAPBus {
if (!newOU.equals(ldapUser.getOu())) {
userService.updateDepId(user.getId(), depIds);
ldapUserService.updateOU(ldapUser.getId(), newOU);
if (ldapTransformUser.isBan()) {
log.info("LDAP-用户同步-被禁止用户部门已更新|ctx=[userId:{},新OU:{}]", user.getId(), newOU);
}
}
// DN变化
if (!ldapTransformUser.getDn().equals(ldapUser.getDn())) {
ldapUserService.updateDN(ldapUser.getId(), ldapTransformUser.getDn());
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (C) 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.common.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
/** LDAP部门同步详情实体 */
@Data
@TableName("ldap_sync_department_detail")
public class LdapSyncDepartmentDetail implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@TableField("record_id")
private Integer recordId;
@TableField("department_id")
private Integer departmentId;
@TableField("uuid")
private String uuid;
@TableField("dn")
private String dn;
@TableField("name")
private String name;
@TableField("action")
private Integer action; // 1-新增2-更新3-删除4-无变化
@TableField("created_at")
private Date createdAt;
private static final long serialVersionUID = 1L;
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.common.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
@Data
@TableName(value = "ldap_sync_record")
public class LdapSyncRecord implements Serializable {
/** */
@TableId(type = IdType.AUTO)
private Integer id;
/** 执行同步的管理员ID0表示系统自动执行 */
@JsonProperty("admin_id")
private Integer adminId;
/** 状态0-进行中1-成功2-失败 */
private Integer status;
/** S3存储中的文件路径 */
@JsonProperty("s3_file_path")
private String s3FilePath;
/** 总部门数量 */
@JsonProperty("total_department_count")
private Integer totalDepartmentCount;
/** 新增部门数量 */
@JsonProperty("created_department_count")
private Integer createdDepartmentCount;
/** 更新部门数量 */
@JsonProperty("updated_department_count")
private Integer updatedDepartmentCount;
/** 删除部门数量 */
@JsonProperty("deleted_department_count")
private Integer deletedDepartmentCount;
/** 总用户数量 */
@JsonProperty("total_user_count")
private Integer totalUserCount;
/** 新增用户数量 */
@JsonProperty("created_user_count")
private Integer createdUserCount;
/** 更新用户数量 */
@JsonProperty("updated_user_count")
private Integer updatedUserCount;
/** 删除用户数量 */
@JsonProperty("deleted_user_count")
private Integer deletedUserCount;
/** 被禁止的用户数量 */
@JsonProperty("banned_user_count")
private Integer bannedUserCount;
/** 错误信息 */
@JsonProperty("error_message")
private String errorMessage;
/** */
@JsonProperty("created_at")
private Date createdAt;
/** */
@JsonProperty("updated_at")
private Date updatedAt;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.common.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
/** LDAP用户同步详情实体 */
@Data
@TableName("ldap_sync_user_detail")
public class LdapSyncUserDetail implements Serializable {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@TableField("record_id")
private Integer recordId;
@TableField("user_id")
private Integer userId;
@TableField("uuid")
private String uuid;
@TableField("dn")
private String dn;
@TableField("cn")
private String cn;
@TableField("uid")
private String uid;
@TableField("email")
private String email;
@TableField("ou")
private String ou;
@TableField("action")
private Integer action; // 1-新增2-更新3-删除4-无变化
@TableField("created_at")
private Date createdAt;
private static final long serialVersionUID = 1L;
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.common.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import xyz.playedu.common.domain.LdapSyncDepartmentDetail;
/** LDAP部门同步详情Mapper */
@Mapper
public interface LdapSyncDepartmentDetailMapper extends BaseMapper<LdapSyncDepartmentDetail> {}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.common.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import xyz.playedu.common.domain.LdapSyncRecord;
/**
* @description 针对表【ldap_sync_record】的数据库操作Mapper
*/
public interface LdapSyncRecordMapper extends BaseMapper<LdapSyncRecord> {}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.common.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import xyz.playedu.common.domain.LdapSyncUserDetail;
/** LDAP用户同步详情Mapper */
@Mapper
public interface LdapSyncUserDetailMapper extends BaseMapper<LdapSyncUserDetail> {}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.common.service;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
import xyz.playedu.common.domain.LdapSyncDepartmentDetail;
/** LDAP部门同步详情服务接口 */
public interface LdapSyncDepartmentDetailService extends IService<LdapSyncDepartmentDetail> {
/**
* 批量创建部门同步详情记录
*
* @param details 部门同步详情记录列表
*/
void batchCreate(List<LdapSyncDepartmentDetail> details);
/**
* 根据同步记录ID和操作类型获取部门同步详情
*
* @param recordId 同步记录ID
* @param action 操作类型1-新增2-更新3-删除4-无变化
* @return 部门同步详情列表
*/
List<LdapSyncDepartmentDetail> getByRecordIdAndAction(Integer recordId, Integer action);
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.common.service;
import com.baomidou.mybatisplus.extension.service.IService;
import xyz.playedu.common.domain.LdapSyncRecord;
import xyz.playedu.common.types.paginate.PaginationResult;
public interface LdapSyncRecordService extends IService<LdapSyncRecord> {
// 创建同步记录
LdapSyncRecord create(Integer adminId);
// 更新同步结果
void updateSyncResult(
Integer id,
Integer status,
String s3FilePath,
Integer totalDepartmentCount,
Integer createdDepartmentCount,
Integer updatedDepartmentCount,
Integer deletedDepartmentCount,
Integer totalUserCount,
Integer createdUserCount,
Integer updatedUserCount,
Integer deletedUserCount,
Integer bannedUserCount);
// 更新同步状态为失败并记录错误信息
void updateSyncFailed(Integer id, String errorMessage);
// 分页查询
PaginationResult<LdapSyncRecord> paginate(Integer page, Integer size);
// 检查是否有进行中的同步任务
boolean hasSyncInProgress();
// 获取最近一次同步记录
LdapSyncRecord getLatestRecord();
}

View File

@@ -0,0 +1,40 @@
/*
* Copyright (C) 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.common.service;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
import xyz.playedu.common.domain.LdapSyncUserDetail;
/** LDAP用户同步详情服务接口 */
public interface LdapSyncUserDetailService extends IService<LdapSyncUserDetail> {
/**
* 批量创建用户同步详情记录
*
* @param details 用户同步详情记录列表
*/
void batchCreate(List<LdapSyncUserDetail> details);
/**
* 根据同步记录ID和操作类型获取用户同步详情
*
* @param recordId 同步记录ID
* @param action 操作类型1-新增2-更新3-删除4-无变化
* @return 用户同步详情列表
*/
List<LdapSyncUserDetail> getByRecordIdAndAction(Integer recordId, Integer action);
}

View File

@@ -38,4 +38,6 @@ public interface LdapUserService extends IService<LdapUser> {
void updateEmail(Integer id, String email);
void updateUid(Integer id, String uid);
void updateDN(Integer id, String dn);
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.common.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.util.List;
import org.springframework.stereotype.Service;
import xyz.playedu.common.domain.LdapSyncDepartmentDetail;
import xyz.playedu.common.mapper.LdapSyncDepartmentDetailMapper;
import xyz.playedu.common.service.LdapSyncDepartmentDetailService;
/** LDAP部门同步详情服务实现类 */
@Service
public class LdapSyncDepartmentDetailServiceImpl
extends ServiceImpl<LdapSyncDepartmentDetailMapper, LdapSyncDepartmentDetail>
implements LdapSyncDepartmentDetailService {
@Override
public void batchCreate(List<LdapSyncDepartmentDetail> details) {
if (details == null || details.isEmpty()) {
return;
}
saveBatch(details);
}
@Override
public List<LdapSyncDepartmentDetail> getByRecordIdAndAction(Integer recordId, Integer action) {
QueryWrapper<LdapSyncDepartmentDetail> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("record_id", recordId);
if (action > 0) {
queryWrapper.eq("action", action);
}
queryWrapper.orderByDesc("id");
return list(queryWrapper);
}
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright (C) 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.common.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.util.Date;
import org.springframework.stereotype.Service;
import xyz.playedu.common.domain.LdapSyncRecord;
import xyz.playedu.common.mapper.LdapSyncRecordMapper;
import xyz.playedu.common.service.LdapSyncRecordService;
import xyz.playedu.common.types.paginate.PaginationResult;
@Service
public class LdapSyncRecordServiceImpl extends ServiceImpl<LdapSyncRecordMapper, LdapSyncRecord>
implements LdapSyncRecordService {
@Override
public LdapSyncRecord create(Integer adminId) {
LdapSyncRecord record = new LdapSyncRecord();
record.setAdminId(adminId);
record.setStatus(0); // 进行中
record.setTotalDepartmentCount(0);
record.setCreatedDepartmentCount(0);
record.setUpdatedDepartmentCount(0);
record.setDeletedDepartmentCount(0);
record.setTotalUserCount(0);
record.setCreatedUserCount(0);
record.setUpdatedUserCount(0);
record.setDeletedUserCount(0);
record.setBannedUserCount(0);
record.setCreatedAt(new Date());
record.setUpdatedAt(new Date());
save(record);
return record;
}
@Override
public void updateSyncResult(
Integer id,
Integer status,
String s3FilePath,
Integer totalDepartmentCount,
Integer createdDepartmentCount,
Integer updatedDepartmentCount,
Integer deletedDepartmentCount,
Integer totalUserCount,
Integer createdUserCount,
Integer updatedUserCount,
Integer deletedUserCount,
Integer bannedUserCount) {
LdapSyncRecord record = new LdapSyncRecord();
record.setId(id);
record.setStatus(status);
record.setS3FilePath(s3FilePath);
record.setTotalDepartmentCount(totalDepartmentCount);
record.setCreatedDepartmentCount(createdDepartmentCount);
record.setUpdatedDepartmentCount(updatedDepartmentCount);
record.setDeletedDepartmentCount(deletedDepartmentCount);
record.setTotalUserCount(totalUserCount);
record.setCreatedUserCount(createdUserCount);
record.setUpdatedUserCount(updatedUserCount);
record.setDeletedUserCount(deletedUserCount);
record.setBannedUserCount(bannedUserCount);
record.setUpdatedAt(new Date());
updateById(record);
}
@Override
public void updateSyncFailed(Integer id, String errorMessage) {
LdapSyncRecord record = new LdapSyncRecord();
record.setId(id);
record.setStatus(2); // 失败
record.setErrorMessage(errorMessage);
record.setUpdatedAt(new Date());
updateById(record);
}
@Override
public PaginationResult<LdapSyncRecord> paginate(Integer page, Integer size) {
Page<LdapSyncRecord> pageObj = new Page<>(page, size);
QueryWrapper<LdapSyncRecord> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("id");
IPage<LdapSyncRecord> iPage = page(pageObj, wrapper);
PaginationResult<LdapSyncRecord> result = new PaginationResult<>();
result.setData(iPage.getRecords());
result.setTotal(iPage.getTotal());
return result;
}
@Override
public boolean hasSyncInProgress() {
QueryWrapper<LdapSyncRecord> wrapper = new QueryWrapper<>();
wrapper.eq("status", 0);
return count(wrapper) > 0;
}
@Override
public LdapSyncRecord getLatestRecord() {
QueryWrapper<LdapSyncRecord> wrapper = new QueryWrapper<>();
wrapper.orderByDesc("id");
wrapper.last("limit 1");
return getOne(wrapper);
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2023 杭州白书科技有限公司
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.playedu.common.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.util.List;
import org.springframework.stereotype.Service;
import xyz.playedu.common.domain.LdapSyncUserDetail;
import xyz.playedu.common.mapper.LdapSyncUserDetailMapper;
import xyz.playedu.common.service.LdapSyncUserDetailService;
/** LDAP用户同步详情服务实现类 */
@Service
public class LdapSyncUserDetailServiceImpl
extends ServiceImpl<LdapSyncUserDetailMapper, LdapSyncUserDetail>
implements LdapSyncUserDetailService {
@Override
public void batchCreate(List<LdapSyncUserDetail> details) {
if (details == null || details.isEmpty()) {
return;
}
saveBatch(details);
}
@Override
public List<LdapSyncUserDetail> getByRecordIdAndAction(Integer recordId, Integer action) {
QueryWrapper<LdapSyncUserDetail> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("record_id", recordId);
if (action > 0) {
queryWrapper.eq("action", action);
}
queryWrapper.orderByDesc("id");
return list(queryWrapper);
}
}

View File

@@ -98,4 +98,12 @@ public class LdapUserServiceImpl extends ServiceImpl<LdapUserMapper, LdapUser>
user.setUid(uid);
updateById(user);
}
@Override
public void updateDN(Integer id, String dn) {
LdapUser user = new LdapUser();
user.setId(id);
user.setDn(dn);
updateById(user);
}
}

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xyz.playedu.common.mapper.LdapSyncDepartmentDetailMapper">
<!-- 通用映射结果集 -->
<resultMap id="BaseResultMap" type="xyz.playedu.common.domain.LdapSyncDepartmentDetail">
<id column="id" property="id"/>
<result column="record_id" property="recordId"/>
<result column="department_id" property="departmentId"/>
<result column="uuid" property="uuid"/>
<result column="dn" property="dn"/>
<result column="name" property="name"/>
<result column="action" property="action"/>
<result column="created_at" property="createdAt"/>
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, record_id, department_id, uuid, dn, name, action, created_at
</sql>
</mapper>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xyz.playedu.common.mapper.LdapSyncRecordMapper">
<resultMap id="BaseResultMap" type="xyz.playedu.common.domain.LdapSyncRecord">
<id property="id" column="id" jdbcType="INTEGER"/>
<result property="adminId" column="admin_id" jdbcType="INTEGER"/>
<result property="status" column="status" jdbcType="TINYINT"/>
<result property="s3FilePath" column="s3_file_path" jdbcType="VARCHAR"/>
<result property="totalDepartmentCount" column="total_department_count" jdbcType="INTEGER"/>
<result property="createdDepartmentCount" column="created_department_count" jdbcType="INTEGER"/>
<result property="updatedDepartmentCount" column="updated_department_count" jdbcType="INTEGER"/>
<result property="deletedDepartmentCount" column="deleted_department_count" jdbcType="INTEGER"/>
<result property="totalUserCount" column="total_user_count" jdbcType="INTEGER"/>
<result property="createdUserCount" column="created_user_count" jdbcType="INTEGER"/>
<result property="updatedUserCount" column="updated_user_count" jdbcType="INTEGER"/>
<result property="deletedUserCount" column="deleted_user_count" jdbcType="INTEGER"/>
<result property="bannedUserCount" column="banned_user_count" jdbcType="INTEGER"/>
<result property="errorMessage" column="error_message" jdbcType="VARCHAR"/>
<result property="createdAt" column="created_at" jdbcType="TIMESTAMP"/>
<result property="updatedAt" column="updated_at" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">
id,admin_id,status,s3_file_path,
total_department_count,created_department_count,updated_department_count,deleted_department_count,
total_user_count,created_user_count,updated_user_count,deleted_user_count,banned_user_count,
error_message,created_at,updated_at
</sql>
</mapper>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xyz.playedu.common.mapper.LdapSyncUserDetailMapper">
<!-- 通用映射结果集 -->
<resultMap id="BaseResultMap" type="xyz.playedu.common.domain.LdapSyncUserDetail">
<id column="id" property="id"/>
<result column="record_id" property="recordId"/>
<result column="user_id" property="userId"/>
<result column="uuid" property="uuid"/>
<result column="dn" property="dn"/>
<result column="cn" property="cn"/>
<result column="uid" property="uid"/>
<result column="email" property="email"/>
<result column="ou" property="ou"/>
<result column="action" property="action"/>
<result column="created_at" property="createdAt"/>
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, record_id, user_id, uuid, dn, cn, uid, email, ou, action, created_at
</sql>
</mapper>