mirror of
https://github.com/PlayEdu/PlayEdu
synced 2025-06-07 17:55:59 +08:00
!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:
parent
b9f600d3bc
commit
c206fa4bf2
31
.cursor/rules/01-project-overview.mdc
Normal file
31
.cursor/rules/01-project-overview.mdc
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
# PlayEdu API Project Overview
|
||||||
|
|
||||||
|
PlayEdu is an online training solution developed by Baishu Technology. The API is built with Java + Spring Boot 3, using a modular approach.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
- [playedu-api](mdc:playedu-api) - Java backend API project
|
||||||
|
- [playedu-admin](mdc:playedu-admin) - Admin frontend
|
||||||
|
- [playedu-pc](mdc:playedu-pc) - PC web interface
|
||||||
|
- [playedu-h5](mdc:playedu-h5) - Mobile web interface
|
||||||
|
|
||||||
|
## API Key Modules
|
||||||
|
- [playedu-api/PlayeduApiApplication.java](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/PlayeduApiApplication.java) - Main application entry point
|
||||||
|
- [playedu-api](mdc:playedu-api/playedu-api) - API module containing controllers and API-specific logic
|
||||||
|
- [playedu-common](mdc:playedu-api/playedu-common) - Common utilities and shared code
|
||||||
|
- [playedu-resource](mdc:playedu-api/playedu-resource) - Resource management module
|
||||||
|
- [playedu-course](mdc:playedu-api/playedu-course) - Course-related functionality
|
||||||
|
- [playedu-system](mdc:playedu-api/playedu-system) - System management functionality
|
||||||
|
|
||||||
|
## Backend vs Frontend Controllers
|
||||||
|
- [Backend Controllers](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/backend) - Admin-facing API endpoints
|
||||||
|
- [Frontend Controllers](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/frontend) - Student-facing API endpoints
|
||||||
|
|
||||||
|
## Development and Deployment
|
||||||
|
- [pom.xml](mdc:playedu-api/pom.xml) - Main Maven configuration file
|
||||||
|
- [Dockerfile](mdc:playedu-api/Dockerfile) - Docker build configuration
|
||||||
|
- [compose.yml](mdc:compose.yml) - Docker Compose configuration
|
32
.cursor/rules/02-api-structure.mdc
Normal file
32
.cursor/rules/02-api-structure.mdc
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
# PlayEdu API Structure
|
||||||
|
|
||||||
|
The API module follows a standard Spring Boot structure with controllers, services, and supporting components.
|
||||||
|
|
||||||
|
## Controller Layout
|
||||||
|
The API endpoints are divided into backend (admin) and frontend (student) controllers:
|
||||||
|
|
||||||
|
### Backend Controllers
|
||||||
|
- [Backend Controllers](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/backend) - Admin management interfaces
|
||||||
|
- [AdminUserController](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/backend/AdminUserController.java) - Administrator management
|
||||||
|
- [CourseController](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/backend/CourseController.java) - Course management
|
||||||
|
- [DepartmentController](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/backend/DepartmentController.java) - Department management
|
||||||
|
- [ResourceController](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/backend/ResourceController.java) - Resource management
|
||||||
|
- [UserController](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/backend/UserController.java) - User management
|
||||||
|
|
||||||
|
### Frontend Controllers
|
||||||
|
- [Frontend Controllers](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/frontend) - Student-facing endpoints
|
||||||
|
- [LoginController](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/frontend/LoginController.java) - Student login
|
||||||
|
- [CourseController](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/frontend/CourseController.java) - Course access
|
||||||
|
|
||||||
|
## API Application Components
|
||||||
|
- [PlayeduApiApplication](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/PlayeduApiApplication.java) - Main application entry point
|
||||||
|
- [Request DTOs](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/request) - Data transfer objects for API requests
|
||||||
|
- [Response Format](mdc:playedu-api/playedu-common/src/main/java/xyz/playedu/common/util/JsonResponse.java) - Standard JSON response format
|
||||||
|
- [Event Handlers](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/event) - Event-driven components
|
||||||
|
- [Scheduled Tasks](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/schedule) - Scheduled/recurring tasks
|
||||||
|
- [Interceptors](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/interceptor) - HTTP request interceptors
|
32
.cursor/rules/03-configuration.mdc
Normal file
32
.cursor/rules/03-configuration.mdc
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
# PlayEdu Configuration Guide
|
||||||
|
|
||||||
|
The PlayEdu application uses standard Spring Boot configuration with YAML files.
|
||||||
|
|
||||||
|
## Application Configuration
|
||||||
|
- [application.yml](mdc:playedu-api/playedu-api/src/main/resources/application.yml) - Main application configuration
|
||||||
|
- [application-dev.yml](mdc:playedu-api/playedu-api/src/main/resources/application-dev.yml) - Development environment overrides
|
||||||
|
|
||||||
|
## Key Configuration Properties
|
||||||
|
- Database connection settings
|
||||||
|
- Redis cache configuration
|
||||||
|
- File storage configuration
|
||||||
|
- Security settings
|
||||||
|
- Cors configuration
|
||||||
|
|
||||||
|
## Build Configuration
|
||||||
|
- [pom.xml](mdc:playedu-api/pom.xml) - Main project Maven POM file
|
||||||
|
- [playedu-api/pom.xml](mdc:playedu-api/playedu-api/pom.xml) - API module POM file
|
||||||
|
- [playedu-common/pom.xml](mdc:playedu-api/playedu-common/pom.xml) - Common module POM file
|
||||||
|
- [playedu-course/pom.xml](mdc:playedu-api/playedu-course/pom.xml) - Course module POM file
|
||||||
|
- [playedu-resource/pom.xml](mdc:playedu-api/playedu-resource/pom.xml) - Resource module POM file
|
||||||
|
- [playedu-system/pom.xml](mdc:playedu-api/playedu-system/pom.xml) - System module POM file
|
||||||
|
|
||||||
|
## Docker Configuration
|
||||||
|
- [Dockerfile](mdc:playedu-api/Dockerfile) - Docker image definition
|
||||||
|
- [Dockerfile.local](mdc:playedu-api/Dockerfile.local) - Local development Docker configuration
|
||||||
|
- [compose.yml](mdc:compose.yml) - Docker Compose service definitions
|
46
.cursor/rules/04-module-structure.mdc
Normal file
46
.cursor/rules/04-module-structure.mdc
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
# PlayEdu Module Structure
|
||||||
|
|
||||||
|
PlayEdu follows a modular architecture with separate modules for different concerns:
|
||||||
|
|
||||||
|
## Module Organization
|
||||||
|
Each module follows a similar structure with domain models, services, and mappers:
|
||||||
|
|
||||||
|
- **playedu-api**: Main API controllers and application entry point
|
||||||
|
- [Controllers](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller) - API endpoints
|
||||||
|
- [Request DTOs](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/request) - Request data objects
|
||||||
|
- [Configuration](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/config) - Module-specific configuration
|
||||||
|
|
||||||
|
- **playedu-common**: Shared utilities, base classes, and common functionality
|
||||||
|
- [Constants](mdc:playedu-api/playedu-common/src/main/java/xyz/playedu/common/constant) - System constants
|
||||||
|
- [Exceptions](mdc:playedu-api/playedu-common/src/main/java/xyz/playedu/common/exception) - Custom exceptions
|
||||||
|
- [Utilities](mdc:playedu-api/playedu-common/src/main/java/xyz/playedu/common/util) - Common utility classes
|
||||||
|
- [Base Models](mdc:playedu-api/playedu-common/src/main/java/xyz/playedu/common/bus) - Base model classes
|
||||||
|
|
||||||
|
- **playedu-resource**: Resource management (files, media, etc.)
|
||||||
|
- [Domain Models](mdc:playedu-api/playedu-resource/src/main/java/xyz/playedu/resource/domain) - Entity classes
|
||||||
|
- [Services](mdc:playedu-api/playedu-resource/src/main/java/xyz/playedu/resource/service) - Business logic
|
||||||
|
- [Mappers](mdc:playedu-api/playedu-resource/src/main/java/xyz/playedu/resource/mapper) - Database access layer
|
||||||
|
|
||||||
|
- **playedu-course**: Course management functionality
|
||||||
|
- [Domain Models](mdc:playedu-api/playedu-course/src/main/java/xyz/playedu/course/domain) - Course entities
|
||||||
|
- [Services](mdc:playedu-api/playedu-course/src/main/java/xyz/playedu/course/service) - Course business logic
|
||||||
|
- [Mappers](mdc:playedu-api/playedu-course/src/main/java/xyz/playedu/course/mapper) - Course data access
|
||||||
|
|
||||||
|
- **playedu-system**: System administration functionality
|
||||||
|
- [Domain Models](mdc:playedu-api/playedu-system/src/main/java/xyz/playedu/system/domain) - System entities
|
||||||
|
- [Services](mdc:playedu-api/playedu-system/src/main/java/xyz/playedu/system/service) - System business logic
|
||||||
|
- [Mappers](mdc:playedu-api/playedu-system/src/main/java/xyz/playedu/system/mapper) - System data access
|
||||||
|
|
||||||
|
## Domain-Driven Design
|
||||||
|
The codebase follows a layered architecture with:
|
||||||
|
- Controllers: Handle API requests and responses
|
||||||
|
- Services: Implement business logic
|
||||||
|
- Mappers: Data access layer (using MyBatis)
|
||||||
|
- Domain models: Entity classes representing business objects
|
||||||
|
|
||||||
|
This modular approach allows for separation of concerns and easier maintainability.
|
45
.cursor/rules/05-development-workflow.mdc
Normal file
45
.cursor/rules/05-development-workflow.mdc
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
# PlayEdu Development Workflow
|
||||||
|
|
||||||
|
This guide outlines the workflow for developing and running the PlayEdu API.
|
||||||
|
|
||||||
|
## Local Development Setup
|
||||||
|
1. Clone the repository
|
||||||
|
2. Use Docker Compose to run the application: `docker-compose up -d`
|
||||||
|
3. Access points:
|
||||||
|
- API: `http://localhost:9700`
|
||||||
|
- Admin backend: `http://localhost:9900` (default credentials: `admin@playedu.xyz / playedu`)
|
||||||
|
- PC web interface: `http://localhost:9800`
|
||||||
|
- H5 mobile interface: `http://localhost:9801`
|
||||||
|
|
||||||
|
## Main Entry Points
|
||||||
|
- [PlayeduApiApplication.java](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/PlayeduApiApplication.java) - Main application class
|
||||||
|
- [application.yml](mdc:playedu-api/playedu-api/src/main/resources/application.yml) - Configuration
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
- Java with Spring Boot 3
|
||||||
|
- MySQL database
|
||||||
|
- Redis for caching
|
||||||
|
- MyBatis for data access
|
||||||
|
- Docker for containerization
|
||||||
|
|
||||||
|
## Development Best Practices
|
||||||
|
- Follow existing code structure when adding new features
|
||||||
|
- Add unit tests for new functionality
|
||||||
|
- Maintain module separation of concerns
|
||||||
|
- Use existing utility classes from `playedu-common`
|
||||||
|
|
||||||
|
## Build Process
|
||||||
|
To build the application:
|
||||||
|
1. Use Maven: `mvn clean package`
|
||||||
|
2. Build Docker image: `docker build -t playedu-api .`
|
||||||
|
3. Run in development mode: `docker-compose up -d`
|
||||||
|
|
||||||
|
## Version Control
|
||||||
|
- Follow standard Git workflow with feature branches
|
||||||
|
- Create pull requests for significant changes
|
||||||
|
- Update CHANGELOG.md for version releases
|
33
.cursor/rules/06-security-model.mdc
Normal file
33
.cursor/rules/06-security-model.mdc
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
# PlayEdu Security Model
|
||||||
|
|
||||||
|
This guide outlines the security model of the PlayEdu application.
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
- [BackendAuthInterceptor](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/interceptor/BackendAuthInterceptor.java) - Backend authentication interceptor
|
||||||
|
- [FrontendAuthInterceptor](mdc:playedu-api/playedu-api/src/main/java/xyz/playedu/api/interceptor/FrontendAuthInterceptor.java) - Frontend authentication interceptor
|
||||||
|
- JWT-based authentication for both frontend and backend users
|
||||||
|
|
||||||
|
## Authorization
|
||||||
|
- Role-based access control for backend users
|
||||||
|
- Department-based content access for frontend users
|
||||||
|
- Course permission enforcement
|
||||||
|
|
||||||
|
## Security Configuration
|
||||||
|
- CORS configuration to prevent cross-site request forgery
|
||||||
|
- Password encryption using BCrypt
|
||||||
|
- Input validation and sanitization
|
||||||
|
|
||||||
|
## Resource Security
|
||||||
|
- Private video storage and delivery
|
||||||
|
- URL-based token authentication for media access
|
||||||
|
- Anti-leech protection for media files
|
||||||
|
|
||||||
|
## Sensitive Data Protection
|
||||||
|
- PII (Personally Identifiable Information) protection
|
||||||
|
- Logging sanitization for sensitive data
|
||||||
|
- Database encryption for critical fields
|
40
.cursor/rules/07-database-structure.mdc
Normal file
40
.cursor/rules/07-database-structure.mdc
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
description:
|
||||||
|
globs:
|
||||||
|
alwaysApply: false
|
||||||
|
---
|
||||||
|
# PlayEdu Database Structure
|
||||||
|
|
||||||
|
This guide outlines the database structure of the PlayEdu application.
|
||||||
|
|
||||||
|
## Database Technology
|
||||||
|
- MySQL database for persistent storage
|
||||||
|
- Redis for caching and session management
|
||||||
|
- MyBatis as the ORM framework
|
||||||
|
|
||||||
|
## Core Tables
|
||||||
|
- **admin_users** - Administrator user accounts
|
||||||
|
- **admin_roles** - Administrator roles for RBAC
|
||||||
|
- **departments** - Organizational departments
|
||||||
|
- **users** - Student/learner accounts
|
||||||
|
- **courses** - Course information
|
||||||
|
- **resources** - Media and document resources
|
||||||
|
- **course_chapters** - Course chapter organization
|
||||||
|
- **course_hour_records** - Learning progress tracking
|
||||||
|
|
||||||
|
## Entity Relationships
|
||||||
|
- Departments have many Users (many-to-many)
|
||||||
|
- Courses have many Chapters (one-to-many)
|
||||||
|
- Courses have many Resources (many-to-many)
|
||||||
|
- Users have progress records for Courses (many-to-many)
|
||||||
|
|
||||||
|
## Database Access
|
||||||
|
- Data access through MyBatis Mappers
|
||||||
|
- [Example Mapper](mdc:playedu-api/playedu-course/src/main/java/xyz/playedu/course/mapper/CourseMapper.java)
|
||||||
|
- XML query definitions in resource XML files
|
||||||
|
- [Example XML](mdc:playedu-api/playedu-course/src/main/resources/mapper/CourseMapper.xml)
|
||||||
|
|
||||||
|
## Data Migration
|
||||||
|
- Managed through SQL scripts
|
||||||
|
- Version controlled database changes
|
||||||
|
- Backup procedures for data safety
|
@ -1,5 +1,8 @@
|
|||||||
## 1.9
|
## 2.0
|
||||||
|
|
||||||
|
- 新增:`LDAP`同步数据统计
|
||||||
|
- 新增:`LDAP`同步的详细记录
|
||||||
|
- 新增:`LDAP`同步的数据下载
|
||||||
- 优化:移除`Redis`运行依赖改为使用内存缓存
|
- 优化:移除`Redis`运行依赖改为使用内存缓存
|
||||||
- 优化:移除本地存储方案`MinIO`的支持改为支持阿里云OSS和腾讯云COS
|
- 优化:移除本地存储方案`MinIO`的支持改为支持阿里云OSS和腾讯云COS
|
||||||
|
|
||||||
|
@ -15,3 +15,4 @@ export * as user from "./user";
|
|||||||
export * as appConfig from "./app-config";
|
export * as appConfig from "./app-config";
|
||||||
export * as dashboard from "./dashboard";
|
export * as dashboard from "./dashboard";
|
||||||
export * as adminLog from "./admin-log";
|
export * as adminLog from "./admin-log";
|
||||||
|
export * as ldap from "./ldap";
|
||||||
|
26
playedu-admin/src/api/ldap.ts
Normal file
26
playedu-admin/src/api/ldap.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import client from "./internal/httpClient";
|
||||||
|
|
||||||
|
// 获取同步记录列表
|
||||||
|
export function getSyncRecords(params: { page?: number; size?: number }) {
|
||||||
|
return client.get("/backend/v1/ldap/sync-records", params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取单条同步记录详情
|
||||||
|
export function getSyncRecordDetail(id: number) {
|
||||||
|
return client.get(`/backend/v1/ldap/sync-records/${id}`, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取同步记录的详细项目
|
||||||
|
export function getSyncRecordDetails(id: number, params: {
|
||||||
|
type: 'department' | 'user';
|
||||||
|
action?: number;
|
||||||
|
page?: number;
|
||||||
|
size?: number
|
||||||
|
}) {
|
||||||
|
return client.get(`/backend/v1/ldap/sync-records/${id}/details`, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载同步记录数据
|
||||||
|
export function downloadSyncRecord(id: number) {
|
||||||
|
return client.get(`/backend/v1/ldap/sync-records/${id}/download`, {});
|
||||||
|
}
|
@ -722,3 +722,10 @@ textarea.ant-input {
|
|||||||
.select-range-modal .ant-tabs-tab + .ant-tabs-tab {
|
.select-range-modal .ant-tabs-tab + .ant-tabs-tab {
|
||||||
margin: 0 0 0 55px;
|
margin: 0 0 0 55px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.clickable-stat {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.clickable-stat:hover .ant-statistic-content {
|
||||||
|
color: #1890ff;
|
||||||
|
}
|
||||||
|
@ -0,0 +1,213 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Modal, Card, Row, Col, Statistic, Divider } from "antd";
|
||||||
|
import { ldap } from "../../../api";
|
||||||
|
import { LdapSyncItemsModal } from ".";
|
||||||
|
import { dateFormat } from "../../../utils/index";
|
||||||
|
|
||||||
|
interface LdapSyncDetailModalProps {
|
||||||
|
record: any;
|
||||||
|
open: boolean;
|
||||||
|
onCancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LdapSyncDetailModal: React.FC<LdapSyncDetailModalProps> = ({
|
||||||
|
record,
|
||||||
|
open,
|
||||||
|
onCancel
|
||||||
|
}) => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [detail, setDetail] = useState<any>(null);
|
||||||
|
const [itemsVisible, setItemsVisible] = useState(false);
|
||||||
|
const [itemsType, setItemsType] = useState<"department" | "user">("department");
|
||||||
|
const [itemsAction, setItemsAction] = useState<number>(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open && record) {
|
||||||
|
loadDetail();
|
||||||
|
}
|
||||||
|
}, [open, record]);
|
||||||
|
|
||||||
|
const loadDetail = () => {
|
||||||
|
setLoading(true);
|
||||||
|
ldap.getSyncRecordDetail(record.id).then((res: any) => {
|
||||||
|
setDetail(res.data);
|
||||||
|
setLoading(false);
|
||||||
|
}).catch(() => {
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const showItems = (type: "department" | "user", action: number) => {
|
||||||
|
setItemsType(type);
|
||||||
|
setItemsAction(action);
|
||||||
|
setItemsVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
title="同步详情"
|
||||||
|
open={open}
|
||||||
|
onCancel={onCancel}
|
||||||
|
width={888}
|
||||||
|
footer={null}
|
||||||
|
>
|
||||||
|
{detail && (
|
||||||
|
<>
|
||||||
|
<Card title="基本信息" loading={loading}>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={8}>
|
||||||
|
<Statistic title="同步ID" value={detail.id} />
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Statistic
|
||||||
|
title="同步状态"
|
||||||
|
value={
|
||||||
|
detail.status === 0 ? "进行中" :
|
||||||
|
detail.status === 1 ? "成功" : "失败"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Statistic title="同步时间" value={dateFormat(detail.created_at)} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Card title="部门同步统计" loading={loading}>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={6}>
|
||||||
|
<div
|
||||||
|
onClick={() => showItems("department", 0)}
|
||||||
|
className="clickable-stat"
|
||||||
|
>
|
||||||
|
<Statistic
|
||||||
|
title="总部门数"
|
||||||
|
value={detail.total_department_count}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<div
|
||||||
|
onClick={() => showItems("department", 1)}
|
||||||
|
className="clickable-stat"
|
||||||
|
>
|
||||||
|
<Statistic
|
||||||
|
title="新增部门"
|
||||||
|
value={detail.created_department_count}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<div
|
||||||
|
onClick={() => showItems("department", 2)}
|
||||||
|
className="clickable-stat"
|
||||||
|
>
|
||||||
|
<Statistic
|
||||||
|
title="更新部门"
|
||||||
|
value={detail.updated_department_count}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<div
|
||||||
|
onClick={() => showItems("department", 3)}
|
||||||
|
className="clickable-stat"
|
||||||
|
>
|
||||||
|
<Statistic
|
||||||
|
title="删除部门"
|
||||||
|
value={detail.deleted_department_count}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Divider />
|
||||||
|
|
||||||
|
<Card title="用户同步统计" loading={loading}>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={6}>
|
||||||
|
<div
|
||||||
|
onClick={() => showItems("user", 0)}
|
||||||
|
className="clickable-stat"
|
||||||
|
>
|
||||||
|
<Statistic
|
||||||
|
title="总用户数"
|
||||||
|
value={detail.total_user_count}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
|
<div
|
||||||
|
onClick={() => showItems("user", 1)}
|
||||||
|
className="clickable-stat"
|
||||||
|
>
|
||||||
|
<Statistic
|
||||||
|
title="新增用户"
|
||||||
|
value={detail.created_user_count}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
|
<div
|
||||||
|
onClick={() => showItems("user", 2)}
|
||||||
|
className="clickable-stat"
|
||||||
|
>
|
||||||
|
<Statistic
|
||||||
|
title="更新用户"
|
||||||
|
value={detail.updated_user_count}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={4}>
|
||||||
|
<div
|
||||||
|
onClick={() => showItems("user", 3)}
|
||||||
|
className="clickable-stat"
|
||||||
|
>
|
||||||
|
<Statistic
|
||||||
|
title="删除用户"
|
||||||
|
value={detail.deleted_user_count}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={6}>
|
||||||
|
<div
|
||||||
|
onClick={() => showItems("user", 5)}
|
||||||
|
className="clickable-stat"
|
||||||
|
>
|
||||||
|
<Statistic
|
||||||
|
title="禁止用户"
|
||||||
|
value={detail.banned_user_count}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{detail.error_message && (
|
||||||
|
<>
|
||||||
|
<Divider />
|
||||||
|
<Card title="错误信息" loading={loading}>
|
||||||
|
<pre>{detail.error_message}</pre>
|
||||||
|
</Card>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
{detail && (
|
||||||
|
<LdapSyncItemsModal
|
||||||
|
recordId={detail.id}
|
||||||
|
type={itemsType}
|
||||||
|
action={itemsAction}
|
||||||
|
open={itemsVisible}
|
||||||
|
onCancel={() => setItemsVisible(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,147 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Modal, Table, Tag } from "antd";
|
||||||
|
import { ldap } from "../../../api";
|
||||||
|
|
||||||
|
interface LdapSyncItemsModalProps {
|
||||||
|
recordId: number;
|
||||||
|
type: "department" | "user";
|
||||||
|
action: number;
|
||||||
|
open: boolean;
|
||||||
|
onCancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LdapSyncItemsModal: React.FC<LdapSyncItemsModalProps> = ({
|
||||||
|
recordId,
|
||||||
|
type,
|
||||||
|
action,
|
||||||
|
open,
|
||||||
|
onCancel
|
||||||
|
}) => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [list, setList] = useState<any[]>([]);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [size, setSize] = useState(10);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
}, [open, page, size, type, action]);
|
||||||
|
|
||||||
|
const loadData = () => {
|
||||||
|
setLoading(true);
|
||||||
|
ldap.getSyncRecordDetails(recordId, {
|
||||||
|
type,
|
||||||
|
action,
|
||||||
|
page,
|
||||||
|
size,
|
||||||
|
}).then((res: any) => {
|
||||||
|
setList(res.data.records);
|
||||||
|
setTotal(res.data.total);
|
||||||
|
setLoading(false);
|
||||||
|
}).catch(() => {
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getActionText = (action: number) => {
|
||||||
|
const actions: any = {
|
||||||
|
department: {
|
||||||
|
1: "新增",
|
||||||
|
2: "更新",
|
||||||
|
3: "删除",
|
||||||
|
4: "无变化"
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
1: "新增",
|
||||||
|
2: "更新",
|
||||||
|
3: "删除",
|
||||||
|
4: "无变化",
|
||||||
|
5: "禁止"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return actions[type][action] || "未知";
|
||||||
|
};
|
||||||
|
|
||||||
|
const getActionColor = (action: number) => {
|
||||||
|
const colors: {[key: number]: string} = {
|
||||||
|
1: "green", // 新增
|
||||||
|
2: "blue", // 更新
|
||||||
|
3: "red", // 删除
|
||||||
|
4: "gray", // 无变化
|
||||||
|
5: "orange" // 禁止
|
||||||
|
};
|
||||||
|
|
||||||
|
return colors[action] || "default";
|
||||||
|
};
|
||||||
|
|
||||||
|
const departmentColumns = [
|
||||||
|
{ title: "ID", dataIndex: "id", key: "id", width: 60 },
|
||||||
|
{ title: "部门名称", dataIndex: "name", key: "name" },
|
||||||
|
{ title: "DN", dataIndex: "dn", key: "dn", ellipsis: true },
|
||||||
|
{ title: "UUID", dataIndex: "uuid", key: "uuid", width: 280 },
|
||||||
|
{
|
||||||
|
title: "操作类型",
|
||||||
|
dataIndex: "action",
|
||||||
|
key: "action",
|
||||||
|
width: 100,
|
||||||
|
render: (actionType: number) => (
|
||||||
|
<Tag color={getActionColor(actionType)}>
|
||||||
|
{getActionText(actionType)}
|
||||||
|
</Tag>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const userColumns = [
|
||||||
|
{ title: "ID", dataIndex: "id", key: "id", width: 60 },
|
||||||
|
{ title: "用户名", dataIndex: "cn", key: "cn" },
|
||||||
|
{ title: "登录名", dataIndex: "uid", key: "uid" },
|
||||||
|
{ title: "邮箱", dataIndex: "email", key: "email" },
|
||||||
|
{ title: "部门", dataIndex: "ou", key: "ou", ellipsis: true },
|
||||||
|
{
|
||||||
|
title: "操作类型",
|
||||||
|
dataIndex: "action",
|
||||||
|
key: "action",
|
||||||
|
width: 100,
|
||||||
|
render: (actionType: number) => (
|
||||||
|
<Tag color={getActionColor(actionType)}>
|
||||||
|
{getActionText(actionType)}
|
||||||
|
</Tag>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const columns = type === "department" ? departmentColumns : userColumns;
|
||||||
|
const title = type === "department" ? "部门同步详情" : "用户同步详情";
|
||||||
|
const actionText = action > 0 ? ` - ${getActionText(action)}` : "";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={`${title}${actionText}`}
|
||||||
|
open={open}
|
||||||
|
onCancel={onCancel}
|
||||||
|
width={900}
|
||||||
|
footer={null}
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={list}
|
||||||
|
rowKey="id"
|
||||||
|
loading={loading}
|
||||||
|
pagination={{
|
||||||
|
total,
|
||||||
|
current: page,
|
||||||
|
pageSize: size,
|
||||||
|
onChange: (page, pageSize) => {
|
||||||
|
setPage(page);
|
||||||
|
setSize(pageSize || 10);
|
||||||
|
},
|
||||||
|
showSizeChanger: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
183
playedu-admin/src/pages/department/components/LdapSyncModal.tsx
Normal file
183
playedu-admin/src/pages/department/components/LdapSyncModal.tsx
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Modal, Table, Button, message } from "antd";
|
||||||
|
import { EyeOutlined, DownloadOutlined, SyncOutlined } from "@ant-design/icons";
|
||||||
|
import { department, ldap } from "../../../api";
|
||||||
|
import { LdapSyncDetailModal } from ".";
|
||||||
|
import { dateFormat } from "../../../utils/index";
|
||||||
|
|
||||||
|
interface LdapSyncModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onCancel: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LdapSyncModal: React.FC<LdapSyncModalProps> = ({
|
||||||
|
open,
|
||||||
|
onCancel,
|
||||||
|
}) => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [syncLoading, setSyncLoading] = useState(false);
|
||||||
|
const [list, setList] = useState<any[]>([]);
|
||||||
|
const [total, setTotal] = useState(0);
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
const [size, setSize] = useState(10);
|
||||||
|
const [detailVisible, setDetailVisible] = useState(false);
|
||||||
|
const [currentRecord, setCurrentRecord] = useState<any>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
loadData();
|
||||||
|
}
|
||||||
|
}, [open, page, size]);
|
||||||
|
|
||||||
|
const loadData = () => {
|
||||||
|
setLoading(true);
|
||||||
|
ldap
|
||||||
|
.getSyncRecords({ page, size })
|
||||||
|
.then((res: any) => {
|
||||||
|
setList(res.data.data);
|
||||||
|
setTotal(res.data.total);
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSync = () => {
|
||||||
|
if (syncLoading) {
|
||||||
|
message.warning("正在同步,请稍后...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSyncLoading(true);
|
||||||
|
department
|
||||||
|
.ldapSync()
|
||||||
|
.then(() => {
|
||||||
|
message.success("同步触发成功");
|
||||||
|
setSyncLoading(false);
|
||||||
|
// 刷新数据列表
|
||||||
|
loadData();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setSyncLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDownload = (id: number) => {
|
||||||
|
ldap
|
||||||
|
.downloadSyncRecord(id)
|
||||||
|
.then((res: any) => {
|
||||||
|
window.open(res.data.url, "_blank");
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
message.error("下载失败");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDetail = (record: any) => {
|
||||||
|
setCurrentRecord(record);
|
||||||
|
setDetailVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ title: "ID", dataIndex: "id", key: "id" },
|
||||||
|
{
|
||||||
|
title: "同步状态",
|
||||||
|
dataIndex: "status",
|
||||||
|
key: "status",
|
||||||
|
render: (status: number) => {
|
||||||
|
if (status === 0)
|
||||||
|
return <span style={{ color: "#faad14" }}>进行中</span>;
|
||||||
|
if (status === 1) return <span style={{ color: "#52c41a" }}>成功</span>;
|
||||||
|
return <span style={{ color: "#f5222d" }}>失败</span>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "部门总数",
|
||||||
|
dataIndex: "total_department_count",
|
||||||
|
key: "total_department_count",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "用户总数",
|
||||||
|
dataIndex: "total_user_count",
|
||||||
|
key: "total_user_count",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "同步时间",
|
||||||
|
dataIndex: "created_at",
|
||||||
|
key: "created_at",
|
||||||
|
render: (text: string) => <span>{dateFormat(text)}</span>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
key: "action",
|
||||||
|
render: (_: any, record: any) => (
|
||||||
|
<div className="d-flex">
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
className="b-link c-red mr-8"
|
||||||
|
icon={<EyeOutlined />}
|
||||||
|
onClick={() => handleDetail(record)}
|
||||||
|
>
|
||||||
|
详情
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
className="b-link c-red"
|
||||||
|
icon={<DownloadOutlined />}
|
||||||
|
onClick={() => handleDownload(record.id)}
|
||||||
|
disabled={!record.s3_file_path}
|
||||||
|
>
|
||||||
|
下载
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
title="LDAP同步记录"
|
||||||
|
open={open}
|
||||||
|
onCancel={onCancel}
|
||||||
|
width={900}
|
||||||
|
footer={null}
|
||||||
|
>
|
||||||
|
<div className="mb-24">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<SyncOutlined />}
|
||||||
|
loading={syncLoading}
|
||||||
|
onClick={handleSync}
|
||||||
|
>
|
||||||
|
创建同步任务
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={list}
|
||||||
|
rowKey="id"
|
||||||
|
loading={loading}
|
||||||
|
pagination={{
|
||||||
|
total,
|
||||||
|
current: page,
|
||||||
|
pageSize: size,
|
||||||
|
onChange: (page, pageSize) => {
|
||||||
|
setPage(page);
|
||||||
|
setSize(pageSize || 10);
|
||||||
|
},
|
||||||
|
showSizeChanger: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
{currentRecord && (
|
||||||
|
<LdapSyncDetailModal
|
||||||
|
record={currentRecord}
|
||||||
|
open={detailVisible}
|
||||||
|
onCancel={() => setDetailVisible(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
3
playedu-admin/src/pages/department/components/index.ts
Normal file
3
playedu-admin/src/pages/department/components/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export { LdapSyncModal } from './LdapSyncModal';
|
||||||
|
export { LdapSyncDetailModal } from './LdapSyncDetailModal';
|
||||||
|
export { LdapSyncItemsModal } from './LdapSyncItemsModal';
|
@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react";
|
|||||||
import { Spin, Button, Tree, Modal, message, Tooltip } from "antd";
|
import { Spin, Button, Tree, Modal, message, Tooltip } from "antd";
|
||||||
// import styles from "./index.module.less";
|
// import styles from "./index.module.less";
|
||||||
import { PlusOutlined, ExclamationCircleFilled } from "@ant-design/icons";
|
import { PlusOutlined, ExclamationCircleFilled } from "@ant-design/icons";
|
||||||
import { department } from "../../api/index";
|
import { department, ldap } from "../../api/index";
|
||||||
import { PerButton } from "../../compenents";
|
import { PerButton } from "../../compenents";
|
||||||
import type { DataNode, TreeProps } from "antd/es/tree";
|
import type { DataNode, TreeProps } from "antd/es/tree";
|
||||||
import { DepartmentCreate } from "./compenents/create";
|
import { DepartmentCreate } from "./compenents/create";
|
||||||
@ -10,6 +10,7 @@ import { DepartmentUpdate } from "./compenents/update";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import { saveDepartmentsAction } from "../../store/system/systemConfigSlice";
|
import { saveDepartmentsAction } from "../../store/system/systemConfigSlice";
|
||||||
|
import { LdapSyncModal } from "./components";
|
||||||
|
|
||||||
const { confirm } = Modal;
|
const { confirm } = Modal;
|
||||||
|
|
||||||
@ -34,6 +35,7 @@ const DepartmentPage = () => {
|
|||||||
const [updateVisible, setUpdateVisible] = useState(false);
|
const [updateVisible, setUpdateVisible] = useState(false);
|
||||||
const [did, setDid] = useState<number>(0);
|
const [did, setDid] = useState<number>(0);
|
||||||
const [modal, contextHolder] = Modal.useModal();
|
const [modal, contextHolder] = Modal.useModal();
|
||||||
|
const [syncModalVisible, setSyncModalVisible] = useState(false);
|
||||||
|
|
||||||
// 是否启用LDAP
|
// 是否启用LDAP
|
||||||
const ldapEnabled = useSelector(
|
const ldapEnabled = useSelector(
|
||||||
@ -395,16 +397,7 @@ const DepartmentPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ldapSync = () => {
|
const ldapSync = () => {
|
||||||
if (loading) {
|
setSyncModalVisible(true);
|
||||||
message.warning("正在同步,请稍后...");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setLoading(true);
|
|
||||||
department.ldapSync().then(() => {
|
|
||||||
message.success("操作成功");
|
|
||||||
setLoading(false);
|
|
||||||
resetData();
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -474,6 +467,10 @@ const DepartmentPage = () => {
|
|||||||
setRefresh(!refresh);
|
setRefresh(!refresh);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<LdapSyncModal
|
||||||
|
open={syncModalVisible}
|
||||||
|
onCancel={() => setSyncModalVisible(false)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
305
playedu-api/docs/ldap-sync-record.md
Normal file
305
playedu-api/docs/ldap-sync-record.md
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
# LDAP同步记录功能
|
||||||
|
|
||||||
|
本文档介绍了PlayEdu系统中LDAP同步记录功能的使用方法和实现细节。
|
||||||
|
|
||||||
|
## 功能概述
|
||||||
|
|
||||||
|
LDAP同步记录功能主要实现了以下功能:
|
||||||
|
|
||||||
|
1. 记录每次LDAP数据同步的统计数据:
|
||||||
|
- 同步的部门和用户总数
|
||||||
|
- 新增、更新、删除的部门和用户数量
|
||||||
|
2. 将同步的LDAP数据保存到S3存储中,方便后续查询和下载
|
||||||
|
3. 记录执行同步操作的管理员ID
|
||||||
|
4. 通过状态控制防止短时间内多次提交同步请求
|
||||||
|
5. 记录每次同步中每个部门和用户的详细变更情况
|
||||||
|
|
||||||
|
## 数据库表
|
||||||
|
|
||||||
|
### 主同步记录表
|
||||||
|
|
||||||
|
系统添加了新的数据库表 `ldap_sync_record`,其结构如下:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE `ldap_sync_record` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`admin_id` int(11) NOT NULL DEFAULT 0 COMMENT '执行同步的管理员ID,0表示系统自动执行',
|
||||||
|
`status` tinyint(4) NOT NULL DEFAULT 0 COMMENT '状态:0-进行中,1-成功,2-失败',
|
||||||
|
`s3_file_path` varchar(255) DEFAULT NULL COMMENT 'S3存储中的文件路径',
|
||||||
|
`total_department_count` int(11) NOT NULL DEFAULT 0 COMMENT '总部门数量',
|
||||||
|
`created_department_count` int(11) NOT NULL DEFAULT 0 COMMENT '新增部门数量',
|
||||||
|
`updated_department_count` int(11) NOT NULL DEFAULT 0 COMMENT '更新部门数量',
|
||||||
|
`deleted_department_count` int(11) NOT NULL DEFAULT 0 COMMENT '删除部门数量',
|
||||||
|
`total_user_count` int(11) NOT NULL DEFAULT 0 COMMENT '总用户数量',
|
||||||
|
`created_user_count` int(11) NOT NULL DEFAULT 0 COMMENT '新增用户数量',
|
||||||
|
`updated_user_count` int(11) NOT NULL DEFAULT 0 COMMENT '更新用户数量',
|
||||||
|
`deleted_user_count` int(11) NOT NULL DEFAULT 0 COMMENT '删除用户数量',
|
||||||
|
`banned_user_count` int(11) NOT NULL DEFAULT 0 COMMENT '被禁止的用户数量',
|
||||||
|
`error_message` text DEFAULT NULL COMMENT '错误信息',
|
||||||
|
`created_at` datetime NOT NULL,
|
||||||
|
`updated_at` datetime NOT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='LDAP同步记录表';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 部门同步详情表
|
||||||
|
|
||||||
|
记录每个部门在同步过程中的详细变更情况:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE `ldap_sync_department_detail` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`record_id` int(11) NOT NULL COMMENT '关联的同步记录ID',
|
||||||
|
`department_id` int(11) DEFAULT NULL COMMENT '关联的部门ID',
|
||||||
|
`uuid` varchar(255) NOT NULL COMMENT 'LDAP部门UUID',
|
||||||
|
`dn` varchar(255) NOT NULL COMMENT 'LDAP部门DN',
|
||||||
|
`name` varchar(255) NOT NULL COMMENT '部门名称',
|
||||||
|
`action` tinyint(4) NOT NULL COMMENT '操作:1-新增,2-更新,3-删除,4-无变化',
|
||||||
|
`created_at` datetime NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `record_id` (`record_id`),
|
||||||
|
KEY `department_id` (`department_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='LDAP部门同步详情表';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 用户同步详情表
|
||||||
|
|
||||||
|
记录每个用户在同步过程中的详细变更情况:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE `ldap_sync_user_detail` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`record_id` int(11) NOT NULL COMMENT '关联的同步记录ID',
|
||||||
|
`user_id` int(11) DEFAULT NULL COMMENT '关联的用户ID',
|
||||||
|
`uuid` varchar(255) NOT NULL COMMENT 'LDAP用户UUID',
|
||||||
|
`dn` varchar(255) NOT NULL COMMENT 'LDAP用户DN',
|
||||||
|
`cn` varchar(255) NOT NULL COMMENT '用户名称',
|
||||||
|
`uid` varchar(255) NOT NULL COMMENT '用户ID/登录名',
|
||||||
|
`email` varchar(255) DEFAULT NULL COMMENT '用户邮箱',
|
||||||
|
`ou` text DEFAULT NULL COMMENT '用户部门路径',
|
||||||
|
`action` tinyint(4) NOT NULL COMMENT '操作:1-新增,2-更新,3-删除,4-无变化,5-禁止',
|
||||||
|
`created_at` datetime NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `record_id` (`record_id`),
|
||||||
|
KEY `user_id` (`user_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='LDAP用户同步详情表';
|
||||||
|
```
|
||||||
|
|
||||||
|
## API接口
|
||||||
|
|
||||||
|
系统提供了以下API接口用于LDAP同步操作和记录管理:
|
||||||
|
|
||||||
|
### 1. 手动触发LDAP同步
|
||||||
|
|
||||||
|
- **URL**: `/backend/v1/ldap/sync`
|
||||||
|
- **方法**: POST
|
||||||
|
- **权限**: `ldap:sync`
|
||||||
|
- **返回数据**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"data": {
|
||||||
|
"record_id": 1 // 同步记录ID
|
||||||
|
},
|
||||||
|
"message": "success"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 获取LDAP同步记录列表
|
||||||
|
|
||||||
|
- **URL**: `/backend/v1/ldap/sync-records`
|
||||||
|
- **方法**: GET
|
||||||
|
- **权限**: `ldap:sync:records`
|
||||||
|
- **参数**:
|
||||||
|
- `page`: 页码,默认1
|
||||||
|
- `size`: 每页条数,默认10
|
||||||
|
- **返回数据**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"data": {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"admin_id": 0,
|
||||||
|
"status": 1,
|
||||||
|
"s3_file_path": "ldap/sync/ldap_sync_1_1620000000000.json",
|
||||||
|
"total_department_count": 10,
|
||||||
|
"created_department_count": 5,
|
||||||
|
"updated_department_count": 3,
|
||||||
|
"deleted_department_count": 2,
|
||||||
|
"total_user_count": 100,
|
||||||
|
"created_user_count": 50,
|
||||||
|
"updated_user_count": 30,
|
||||||
|
"deleted_user_count": 20,
|
||||||
|
"banned_user_count": 0,
|
||||||
|
"error_message": null,
|
||||||
|
"created_at": "2023-08-01 12:00:00",
|
||||||
|
"updated_at": "2023-08-01 12:01:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 100
|
||||||
|
},
|
||||||
|
"message": "success"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 获取LDAP同步记录详情
|
||||||
|
|
||||||
|
- **URL**: `/backend/v1/ldap/sync-records/{id}`
|
||||||
|
- **方法**: GET
|
||||||
|
- **权限**: `ldap:sync:records`
|
||||||
|
- **返回数据**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"admin_id": 0,
|
||||||
|
"status": 1,
|
||||||
|
"s3_file_path": "ldap/sync/ldap_sync_1_1620000000000.json",
|
||||||
|
"total_department_count": 10,
|
||||||
|
"created_department_count": 5,
|
||||||
|
"updated_department_count": 3,
|
||||||
|
"deleted_department_count": 2,
|
||||||
|
"total_user_count": 100,
|
||||||
|
"created_user_count": 50,
|
||||||
|
"updated_user_count": 30,
|
||||||
|
"deleted_user_count": 20,
|
||||||
|
"banned_user_count": 0,
|
||||||
|
"error_message": null,
|
||||||
|
"created_at": "2023-08-01 12:00:00",
|
||||||
|
"updated_at": "2023-08-01 12:01:00"
|
||||||
|
},
|
||||||
|
"message": "success"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 获取同步详情
|
||||||
|
|
||||||
|
- **URL**: `/backend/v1/ldap/sync-records/{id}/details`
|
||||||
|
- **方法**: GET
|
||||||
|
- **权限**: `ldap:sync:records`
|
||||||
|
- **参数**:
|
||||||
|
- `type`: 详情类型,`department`=部门,`user`=用户
|
||||||
|
- `action`: 操作类型,当type=department时:0=全部,1=新增,2=更新,3=删除,4=无变化;当type=user时:0=全部,1=新增,2=更新,3=删除,4=无变化,5=禁止,默认0
|
||||||
|
- `page`: 页码,默认1
|
||||||
|
- `size`: 每页条数,默认10
|
||||||
|
- **返回数据**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"data": {
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
// 当type=department时返回部门详情
|
||||||
|
"id": 1,
|
||||||
|
"record_id": 1,
|
||||||
|
"department_id": 10,
|
||||||
|
"uuid": "12345678-1234-1234-1234-123456789012",
|
||||||
|
"dn": "ou=HR,dc=example,dc=com",
|
||||||
|
"name": "HR",
|
||||||
|
"action": 1,
|
||||||
|
"created_at": "2023-08-01 12:00:00"
|
||||||
|
}
|
||||||
|
// 或当type=user时返回用户详情
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"record_id": 1,
|
||||||
|
"user_id": 100,
|
||||||
|
"uuid": "12345678-1234-1234-1234-123456789012",
|
||||||
|
"dn": "cn=John Doe,ou=HR,dc=example,dc=com",
|
||||||
|
"cn": "John Doe",
|
||||||
|
"uid": "johndoe",
|
||||||
|
"email": "john.doe@example.com",
|
||||||
|
"ou": "HR",
|
||||||
|
"action": 1,
|
||||||
|
"created_at": "2023-08-01 12:00:00"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total": 100,
|
||||||
|
"size": 10,
|
||||||
|
"current": 1,
|
||||||
|
"pages": 10
|
||||||
|
},
|
||||||
|
"message": "success"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 下载LDAP同步记录数据
|
||||||
|
|
||||||
|
- **URL**: `/backend/v1/ldap/sync-records/{id}/download`
|
||||||
|
- **方法**: GET
|
||||||
|
- **权限**: `ldap:sync:records`
|
||||||
|
- **返回数据**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 0,
|
||||||
|
"data": {
|
||||||
|
"url": "https://your-s3-domain.com/ldap/sync/ldap_sync_1_1620000000000.json"
|
||||||
|
},
|
||||||
|
"message": "success"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 定时同步
|
||||||
|
|
||||||
|
系统会每小时自动执行一次LDAP同步,同步过程和统计数据会记录到 `ldap_sync_record` 表中。自动同步时 `admin_id` 字段为0。
|
||||||
|
|
||||||
|
## 权限说明
|
||||||
|
|
||||||
|
为了使用LDAP同步记录功能,需要为管理员角色分配以下权限:
|
||||||
|
|
||||||
|
- `ldap:sync`: 允许手动触发LDAP同步
|
||||||
|
- `ldap:sync:records`: 允许查看LDAP同步记录
|
||||||
|
|
||||||
|
## 同步状态说明
|
||||||
|
|
||||||
|
LDAP同步记录的状态字段(`status`)有以下值:
|
||||||
|
|
||||||
|
- `0`: 进行中
|
||||||
|
- `1`: 成功
|
||||||
|
- `2`: 失败
|
||||||
|
|
||||||
|
当状态为2(失败)时,错误信息会记录在 `error_message` 字段中。
|
||||||
|
|
||||||
|
## 操作类型说明
|
||||||
|
|
||||||
|
详细同步记录中的操作类型(`action`)有以下值:
|
||||||
|
|
||||||
|
### 部门操作类型:
|
||||||
|
- `1`: 新增 - 首次在LDAP中发现的部门
|
||||||
|
- `2`: 更新 - 已存在但信息发生变化的部门
|
||||||
|
- `3`: 删除 - 在LDAP中不再存在的部门
|
||||||
|
- `4`: 无变化 - 已存在且信息未发生变化的部门
|
||||||
|
|
||||||
|
### 用户操作类型:
|
||||||
|
- `1`: 新增 - 首次在LDAP中发现的用户
|
||||||
|
- `2`: 更新 - 已存在但信息发生变化的用户
|
||||||
|
- `3`: 删除 - 在LDAP中不再存在的用户
|
||||||
|
- `4`: 无变化 - 已存在且信息未发生变化的用户
|
||||||
|
- `5`: 禁止 - 在LDAP中被标记为禁止状态的用户
|
||||||
|
|
||||||
|
## S3存储
|
||||||
|
|
||||||
|
同步的LDAP数据会以JSON格式保存到S3存储中,路径格式为:
|
||||||
|
|
||||||
|
```
|
||||||
|
ldap/sync/ldap_sync_{记录ID}_{时间戳}.json
|
||||||
|
```
|
||||||
|
|
||||||
|
JSON文件包含完整的同步数据,包括所有部门和用户的信息。
|
||||||
|
|
||||||
|
## 实现细节
|
||||||
|
|
||||||
|
系统在每次LDAP同步时都会进行以下操作:
|
||||||
|
|
||||||
|
1. 创建主同步记录,初始状态为"进行中"
|
||||||
|
2. 获取LDAP配置信息并查询所有LDAP部门和用户数据
|
||||||
|
3. 收集统计信息并保存到S3
|
||||||
|
4. 收集每个部门和用户的详细变更情况
|
||||||
|
5. 执行实际的同步操作,包括部门同步和用户同步
|
||||||
|
6. 保存部门和用户的详细变更记录
|
||||||
|
7. 更新主同步记录状态为"成功"
|
||||||
|
|
||||||
|
如果同步过程中出现异常,系统会捕获异常信息并更新主同步记录状态为"失败"。
|
@ -334,8 +334,26 @@ public class DepartmentController {
|
|||||||
@Log(title = "部门-LDAP同步", businessType = BusinessTypeConstant.INSERT)
|
@Log(title = "部门-LDAP同步", businessType = BusinessTypeConstant.INSERT)
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public JsonResponse ldapSync() {
|
public JsonResponse ldapSync() {
|
||||||
ldapBus.departmentSync();
|
try {
|
||||||
ldapBus.userSync();
|
// 检查是否启用LDAP
|
||||||
return JsonResponse.success();
|
if (!ldapBus.enabledLDAP()) {
|
||||||
|
return JsonResponse.error("未配置LDAP服务");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有进行中的同步任务
|
||||||
|
if (ldapBus.hasSyncInProgress()) {
|
||||||
|
return JsonResponse.error("有正在进行的LDAP同步任务,请稍后再试");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用当前管理员ID执行同步
|
||||||
|
Integer recordId = ldapBus.syncAndRecord(BCtx.getId());
|
||||||
|
|
||||||
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
data.put("record_id", recordId);
|
||||||
|
|
||||||
|
return JsonResponse.data(data);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return JsonResponse.error("LDAP同步失败: " + e.getMessage());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.api.controller.backend;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import xyz.playedu.common.annotation.BackendPermission;
|
||||||
|
import xyz.playedu.common.annotation.Log;
|
||||||
|
import xyz.playedu.common.constant.BPermissionConstant;
|
||||||
|
import xyz.playedu.common.constant.BusinessTypeConstant;
|
||||||
|
import xyz.playedu.common.domain.LdapSyncRecord;
|
||||||
|
import xyz.playedu.common.service.AppConfigService;
|
||||||
|
import xyz.playedu.common.service.LdapSyncRecordService;
|
||||||
|
import xyz.playedu.common.types.JsonResponse;
|
||||||
|
import xyz.playedu.common.types.config.S3Config;
|
||||||
|
import xyz.playedu.common.types.paginate.PaginationResult;
|
||||||
|
import xyz.playedu.common.util.S3Util;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/backend/v1/ldap")
|
||||||
|
public class LdapController {
|
||||||
|
|
||||||
|
@Autowired private LdapSyncRecordService ldapSyncRecordService;
|
||||||
|
@Autowired private AppConfigService appConfigService;
|
||||||
|
|
||||||
|
@BackendPermission(slug = BPermissionConstant.DEPARTMENT_CUD)
|
||||||
|
@GetMapping("/sync-records")
|
||||||
|
@Log(title = "LDAP-同步记录列表", businessType = BusinessTypeConstant.GET)
|
||||||
|
public JsonResponse syncRecords(
|
||||||
|
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(value = "size", defaultValue = "10") Integer size) {
|
||||||
|
|
||||||
|
PaginationResult<LdapSyncRecord> result = ldapSyncRecordService.paginate(page, size);
|
||||||
|
|
||||||
|
HashMap<String, Object> data = new HashMap<>();
|
||||||
|
data.put("data", result.getData());
|
||||||
|
data.put("total", result.getTotal());
|
||||||
|
|
||||||
|
return JsonResponse.data(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BackendPermission(slug = BPermissionConstant.DEPARTMENT_CUD)
|
||||||
|
@GetMapping("/sync-records/{id}")
|
||||||
|
@Log(title = "LDAP-同步记录详情", businessType = BusinessTypeConstant.GET)
|
||||||
|
public JsonResponse syncRecordDetail(@PathVariable Integer id) {
|
||||||
|
LdapSyncRecord record = ldapSyncRecordService.getById(id);
|
||||||
|
if (record == null) {
|
||||||
|
return JsonResponse.error("记录不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonResponse.data(record);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BackendPermission(slug = BPermissionConstant.DEPARTMENT_CUD)
|
||||||
|
@GetMapping("/sync-records/{id}/download")
|
||||||
|
@Log(title = "LDAP-同步记录下载", businessType = BusinessTypeConstant.GET)
|
||||||
|
public JsonResponse syncRecordDownload(@PathVariable Integer id) {
|
||||||
|
LdapSyncRecord record = ldapSyncRecordService.getById(id);
|
||||||
|
if (record == null) {
|
||||||
|
return JsonResponse.error("记录不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (record.getS3FilePath() == null || record.getS3FilePath().isEmpty()) {
|
||||||
|
return JsonResponse.error("同步记录文件不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 生成下载URL
|
||||||
|
S3Config s3Config = appConfigService.getS3Config();
|
||||||
|
S3Util s3Util = new S3Util(s3Config);
|
||||||
|
String url = s3Util.generateEndpointPreSignUrl(record.getS3FilePath());
|
||||||
|
|
||||||
|
HashMap<String, Object> data = new HashMap<>();
|
||||||
|
data.put("url", url);
|
||||||
|
|
||||||
|
return JsonResponse.data(data);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return JsonResponse.error("生成下载链接失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* 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.api.controller.backend;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
import xyz.playedu.common.annotation.BackendPermission;
|
||||||
|
import xyz.playedu.common.constant.BPermissionConstant;
|
||||||
|
import xyz.playedu.common.domain.LdapSyncDepartmentDetail;
|
||||||
|
import xyz.playedu.common.domain.LdapSyncUserDetail;
|
||||||
|
import xyz.playedu.common.service.LdapSyncDepartmentDetailService;
|
||||||
|
import xyz.playedu.common.service.LdapSyncUserDetailService;
|
||||||
|
import xyz.playedu.common.types.JsonResponse;
|
||||||
|
|
||||||
|
/** LDAP同步详情控制器 */
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/backend/v1/ldap")
|
||||||
|
public class LdapSyncDetailController {
|
||||||
|
|
||||||
|
@Autowired private LdapSyncDepartmentDetailService ldapSyncDepartmentDetailService;
|
||||||
|
|
||||||
|
@Autowired private LdapSyncUserDetailService ldapSyncUserDetailService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取同步详情
|
||||||
|
*
|
||||||
|
* @param id 同步记录ID
|
||||||
|
* @param type 详情类型:department-部门,user-用户
|
||||||
|
* @param action 操作类型:
|
||||||
|
* - 部门:1-新增,2-更新,3-删除,4-无变化
|
||||||
|
* - 用户:1-新增,2-更新,3-删除,4-无变化,5-禁止
|
||||||
|
* @param page 页码
|
||||||
|
* @param size 每页数量
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
@BackendPermission(slug = BPermissionConstant.DEPARTMENT_CUD)
|
||||||
|
@GetMapping("/sync-records/{id}/details")
|
||||||
|
public JsonResponse getDetails(
|
||||||
|
@PathVariable Integer id,
|
||||||
|
@RequestParam String type,
|
||||||
|
@RequestParam(defaultValue = "0") Integer action,
|
||||||
|
@RequestParam(defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(defaultValue = "10") Integer size) {
|
||||||
|
|
||||||
|
if ("department".equals(type)) {
|
||||||
|
// 部门同步详情
|
||||||
|
QueryWrapper<LdapSyncDepartmentDetail> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("record_id", id);
|
||||||
|
if (action > 0) {
|
||||||
|
queryWrapper.eq("action", action);
|
||||||
|
}
|
||||||
|
queryWrapper.orderByDesc("id");
|
||||||
|
|
||||||
|
IPage<LdapSyncDepartmentDetail> pageResult =
|
||||||
|
ldapSyncDepartmentDetailService.page(new Page<>(page, size), queryWrapper);
|
||||||
|
|
||||||
|
return JsonResponse.data(pageResult);
|
||||||
|
} else if ("user".equals(type)) {
|
||||||
|
// 用户同步详情
|
||||||
|
QueryWrapper<LdapSyncUserDetail> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("record_id", id);
|
||||||
|
if (action > 0) {
|
||||||
|
queryWrapper.eq("action", action);
|
||||||
|
}
|
||||||
|
queryWrapper.orderByDesc("id");
|
||||||
|
|
||||||
|
IPage<LdapSyncUserDetail> pageResult =
|
||||||
|
ldapSyncUserDetailService.page(new Page<>(page, size), queryWrapper);
|
||||||
|
|
||||||
|
return JsonResponse.data(pageResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonResponse.error("不支持的详情类型");
|
||||||
|
}
|
||||||
|
}
|
@ -43,17 +43,11 @@ public class LDAPSchedule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ldapBus.departmentSync();
|
// 使用新的同步记录功能
|
||||||
|
ldapBus.syncAndRecord(0); // 0表示系统自动执行
|
||||||
|
log.info("LDAP同步成功");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("LDAP-部门同步失败", e);
|
log.error("LDAP同步失败", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
ldapBus.userSync();
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("LDAP-学员同步失败", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("LDAP同步成功");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
package xyz.playedu.common.bus;
|
package xyz.playedu.common.bus;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -26,12 +30,17 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import xyz.playedu.common.domain.Department;
|
import xyz.playedu.common.domain.Department;
|
||||||
import xyz.playedu.common.domain.LdapDepartment;
|
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.LdapUser;
|
||||||
import xyz.playedu.common.domain.User;
|
import xyz.playedu.common.domain.User;
|
||||||
import xyz.playedu.common.exception.NotFoundException;
|
import xyz.playedu.common.exception.NotFoundException;
|
||||||
import xyz.playedu.common.service.*;
|
import xyz.playedu.common.service.*;
|
||||||
import xyz.playedu.common.types.LdapConfig;
|
import xyz.playedu.common.types.LdapConfig;
|
||||||
|
import xyz.playedu.common.types.config.S3Config;
|
||||||
import xyz.playedu.common.util.HelperUtil;
|
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.LdapTransformDepartment;
|
||||||
import xyz.playedu.common.util.ldap.LdapTransformUser;
|
import xyz.playedu.common.util.ldap.LdapTransformUser;
|
||||||
import xyz.playedu.common.util.ldap.LdapUtil;
|
import xyz.playedu.common.util.ldap.LdapUtil;
|
||||||
@ -50,16 +59,381 @@ public class LDAPBus {
|
|||||||
|
|
||||||
@Autowired private UserService userService;
|
@Autowired private UserService userService;
|
||||||
|
|
||||||
|
@Autowired private LdapSyncRecordService ldapSyncRecordService;
|
||||||
|
|
||||||
|
@Autowired private LdapSyncDepartmentDetailService ldapSyncDepartmentDetailService;
|
||||||
|
|
||||||
|
@Autowired private LdapSyncUserDetailService ldapSyncUserDetailService;
|
||||||
|
|
||||||
public boolean enabledLDAP() {
|
public boolean enabledLDAP() {
|
||||||
return appConfigService.enabledLdapLogin();
|
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 执行同步的管理员ID,0为系统自动执行
|
||||||
|
* @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()) {
|
if (ouList == null || ouList.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -203,11 +577,12 @@ public class LDAPBus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void userSync() throws NamingException, IOException {
|
/**
|
||||||
LdapConfig ldapConfig = appConfigService.ldapConfig();
|
* 执行用户同步 - 提供现有的LDAP用户数据
|
||||||
|
*
|
||||||
List<LdapTransformUser> userList = LdapUtil.users(ldapConfig, ldapConfig.getBaseDN());
|
* @param userList 已获取的LDAP用户数据
|
||||||
|
*/
|
||||||
|
public void userSync(List<LdapTransformUser> userList) {
|
||||||
if (userList == null || userList.isEmpty()) {
|
if (userList == null || userList.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -216,12 +591,18 @@ public class LDAPBus {
|
|||||||
|
|
||||||
for (LdapTransformUser ldapTransformUser : userList) {
|
for (LdapTransformUser ldapTransformUser : userList) {
|
||||||
if (ldapTransformUser.isBan()) {
|
if (ldapTransformUser.isBan()) {
|
||||||
log.info(
|
// 检查用户是否已在系统中存在
|
||||||
"LDAP-用户同步-用户被禁止|ctx=[dn:{},uuid={}]",
|
LdapUser existingLdapUser = ldapUserService.findByUUID(ldapTransformUser.getId());
|
||||||
ldapTransformUser.getDn(),
|
if (existingLdapUser == null) {
|
||||||
ldapTransformUser.getId());
|
// 对于新的被禁止用户,不同步到系统
|
||||||
continue;
|
log.info(
|
||||||
|
"LDAP-用户同步-新用户被禁止不同步|ctx=[dn:{},uuid={}]",
|
||||||
|
ldapTransformUser.getDn(),
|
||||||
|
ldapTransformUser.getId());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
singleUserSync(ldapTransformUser, defaultAvatar);
|
singleUserSync(ldapTransformUser, defaultAvatar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -326,6 +707,15 @@ public class LDAPBus {
|
|||||||
if (!newOU.equals(ldapUser.getOu())) {
|
if (!newOU.equals(ldapUser.getOu())) {
|
||||||
userService.updateDepId(user.getId(), depIds);
|
userService.updateDepId(user.getId(), depIds);
|
||||||
ldapUserService.updateOU(ldapUser.getId(), newOU);
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
}
|
@ -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;
|
||||||
|
|
||||||
|
/** 执行同步的管理员ID,0表示系统自动执行 */
|
||||||
|
@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;
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
@ -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> {}
|
@ -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> {}
|
@ -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> {}
|
@ -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);
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -38,4 +38,6 @@ public interface LdapUserService extends IService<LdapUser> {
|
|||||||
void updateEmail(Integer id, String email);
|
void updateEmail(Integer id, String email);
|
||||||
|
|
||||||
void updateUid(Integer id, String uid);
|
void updateUid(Integer id, String uid);
|
||||||
|
|
||||||
|
void updateDN(Integer id, String dn);
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -98,4 +98,12 @@ public class LdapUserServiceImpl extends ServiceImpl<LdapUserMapper, LdapUser>
|
|||||||
user.setUid(uid);
|
user.setUid(uid);
|
||||||
updateById(user);
|
updateById(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateDN(Integer id, String dn) {
|
||||||
|
LdapUser user = new LdapUser();
|
||||||
|
user.setId(id);
|
||||||
|
user.setDn(dn);
|
||||||
|
updateById(user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -731,6 +731,87 @@ public class MigrationCheck implements CommandLineRunner {
|
|||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
add(
|
||||||
|
new HashMap<>() {
|
||||||
|
{
|
||||||
|
put("table", "ldap_sync_record");
|
||||||
|
put("name", "20250517_13_23_ldap_sync_record");
|
||||||
|
put(
|
||||||
|
"sql",
|
||||||
|
"""
|
||||||
|
CREATE TABLE `ldap_sync_record` (
|
||||||
|
`id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`admin_id` int NOT NULL DEFAULT '0' COMMENT '执行同步的管理员ID,0表示系统自动执行',
|
||||||
|
`status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0-进行中,1-成功,2-失败',
|
||||||
|
`s3_file_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'S3存储中的文件路径',
|
||||||
|
`total_department_count` int NOT NULL DEFAULT '0' COMMENT '总部门数量',
|
||||||
|
`created_department_count` int NOT NULL DEFAULT '0' COMMENT '新增部门数量',
|
||||||
|
`updated_department_count` int NOT NULL DEFAULT '0' COMMENT '更新部门数量',
|
||||||
|
`deleted_department_count` int NOT NULL DEFAULT '0' COMMENT '删除部门数量',
|
||||||
|
`total_user_count` int NOT NULL DEFAULT '0' COMMENT '总用户数量',
|
||||||
|
`created_user_count` int NOT NULL DEFAULT '0' COMMENT '新增用户数量',
|
||||||
|
`updated_user_count` int NOT NULL DEFAULT '0' COMMENT '更新用户数量',
|
||||||
|
`deleted_user_count` int NOT NULL DEFAULT '0' COMMENT '删除用户数量',
|
||||||
|
`banned_user_count` int NOT NULL DEFAULT '0' COMMENT '被禁止的用户数量',
|
||||||
|
`error_message` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT '错误信息',
|
||||||
|
`created_at` datetime NOT NULL,
|
||||||
|
`updated_at` datetime NOT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='LDAP同步记录表';
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
add(
|
||||||
|
new HashMap<>() {
|
||||||
|
{
|
||||||
|
put("table", "ldap_sync_department_detail");
|
||||||
|
put("name", "20250519_10_25_01_ldap_sync_department_detail");
|
||||||
|
put(
|
||||||
|
"sql",
|
||||||
|
"""
|
||||||
|
CREATE TABLE `ldap_sync_department_detail` (
|
||||||
|
`id` int NOT NULL AUTO_INCREMENT,
|
||||||
|
`record_id` int NOT NULL COMMENT '关联的同步记录ID',
|
||||||
|
`department_id` int NOT NULL DEFAULT '0' COMMENT '关联的部门ID',
|
||||||
|
`uuid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'LDAP部门UUID',
|
||||||
|
`dn` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'LDAP部门DN',
|
||||||
|
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '部门名称',
|
||||||
|
`action` tinyint NOT NULL COMMENT '操作:1-新增,2-更新,3-删除,4-无变化',
|
||||||
|
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `record_id` (`record_id`),
|
||||||
|
KEY `department_id` (`department_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='LDAP部门同步详情表';
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
add(
|
||||||
|
new HashMap<>() {
|
||||||
|
{
|
||||||
|
put("table", "ldap_sync_user_detail");
|
||||||
|
put("name", "20250519_10_25_02_ldap_sync_user_detail");
|
||||||
|
put(
|
||||||
|
"sql",
|
||||||
|
"""
|
||||||
|
CREATE TABLE `ldap_sync_user_detail` (
|
||||||
|
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||||
|
`record_id` int NOT NULL COMMENT '关联的同步记录ID',
|
||||||
|
`user_id` bigint NOT NULL DEFAULT '0' COMMENT '关联的用户ID',
|
||||||
|
`uuid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'LDAP用户UUID',
|
||||||
|
`dn` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'LDAP用户DN',
|
||||||
|
`cn` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名称',
|
||||||
|
`uid` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户ID/登录名',
|
||||||
|
`email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '用户邮箱',
|
||||||
|
`ou` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '用户部门路径',
|
||||||
|
`action` tinyint NOT NULL COMMENT '操作:1-新增,2-更新,3-删除,4-无变化,5-禁止',
|
||||||
|
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `record_id` (`record_id`),
|
||||||
|
KEY `user_id` (`user_id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='LDAP用户同步详情表';
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user