From c206fa4bf2191d33ab3ee3cc3e6bf0e1d1c89bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E4=B9=A6=E7=A7=91=E6=8A=80?= <412656+myteng@user.noreply.gitee.com> Date: Mon, 19 May 2025 06:25:34 +0000 Subject: [PATCH] =?UTF-8?q?!13=20LDAP=E4=BC=98=E5=8C=96=E5=A2=9E=E5=BC=BA?= =?UTF-8?q?=20*=20cursorrules=20*=20fixed:=20ldap=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E7=9A=84=E9=83=A8=E9=97=A8=E8=AE=B0=E5=BD=95name=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E9=94=99=E8=AF=AF=20*=20=E4=B8=BB=E9=A2=98=E8=89=B2?= =?UTF-8?q?=E4=B8=80=E8=87=B4=20*=20changelog=20*=20admin=E6=8E=A5?= =?UTF-8?q?=E5=85=A5ldap=E5=90=8C=E6=AD=A5=E6=95=B0=E6=8D=AE=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1=20*=20ldap=E5=90=8C=E6=AD=A5=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=E6=8E=A5=E5=8F=A3=E5=90=88=E5=B9=B6=20*=20fi?= =?UTF-8?q?xed:=20=E5=B7=B2=E5=90=8C=E6=AD=A5=E8=A2=AB=E7=A6=81=E7=94=A8?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E7=9A=84DN=E6=9B=B4=E6=96=B0=20*=20=E5=B7=B2?= =?UTF-8?q?=E7=BB=8F=E5=90=8C=E6=AD=A5=E7=9A=84LDAP=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E8=A2=AB=E7=A6=81=E6=AD=A2=E5=8F=AF=E4=BB=A5=E7=BB=A7=E7=BB=AD?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=20*=20=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=20*=20=E6=96=B0=E5=A2=9ELDAP=E5=90=8C=E6=AD=A5=E7=9A=84?= =?UTF-8?q?=E8=AF=A6=E7=BB=86=E8=AE=B0=E5=BD=95=20*=20=E6=96=B0=E5=A2=9ELD?= =?UTF-8?q?AP=E7=A6=81=E6=AD=A2=E7=94=A8=E6=88=B7=E7=9A=84=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E9=87=8F=E7=BB=9F=E8=AE=A1=20*=20=E4=BC=98=E5=8C=96LD?= =?UTF-8?q?AP=E6=8B=89=E5=8F=96=E6=95=B0=E6=8D=AE=E7=9A=84=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E4=BD=BF=E7=94=A8=20*=20=E4=BC=98=E5=8C=96LDAP?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=20*=20ldap=E5=90=8C=E6=AD=A5=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=20*=20cursor=20rules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cursor/rules/01-project-overview.mdc | 31 ++ .cursor/rules/02-api-structure.mdc | 32 ++ .cursor/rules/03-configuration.mdc | 32 ++ .cursor/rules/04-module-structure.mdc | 46 ++ .cursor/rules/05-development-workflow.mdc | 45 ++ .cursor/rules/06-security-model.mdc | 33 ++ .cursor/rules/07-database-structure.mdc | 40 ++ CHANGELOG.md | 5 +- playedu-admin/src/api/index.ts | 1 + playedu-admin/src/api/ldap.ts | 26 ++ playedu-admin/src/index.less | 7 + .../components/LdapSyncDetailModal.tsx | 213 +++++++++ .../components/LdapSyncItemsModal.tsx | 147 ++++++ .../department/components/LdapSyncModal.tsx | 183 ++++++++ .../src/pages/department/components/index.ts | 3 + playedu-admin/src/pages/department/index.tsx | 19 +- playedu-api/docs/ldap-sync-record.md | 305 +++++++++++++ .../backend/DepartmentController.java | 24 +- .../controller/backend/LdapController.java | 95 ++++ .../backend/LdapSyncDetailController.java | 91 ++++ .../playedu/api/schedule/LDAPSchedule.java | 14 +- .../java/xyz/playedu/common/bus/LDAPBus.java | 418 +++++++++++++++++- .../domain/LdapSyncDepartmentDetail.java | 56 +++ .../playedu/common/domain/LdapSyncRecord.java | 95 ++++ .../common/domain/LdapSyncUserDetail.java | 65 +++ .../LdapSyncDepartmentDetailMapper.java | 24 + .../common/mapper/LdapSyncRecordMapper.java | 24 + .../mapper/LdapSyncUserDetailMapper.java | 24 + .../LdapSyncDepartmentDetailService.java | 40 ++ .../common/service/LdapSyncRecordService.java | 52 +++ .../service/LdapSyncUserDetailService.java | 40 ++ .../common/service/LdapUserService.java | 2 + .../LdapSyncDepartmentDetailServiceImpl.java | 50 +++ .../impl/LdapSyncRecordServiceImpl.java | 127 ++++++ .../impl/LdapSyncUserDetailServiceImpl.java | 50 +++ .../service/impl/LdapUserServiceImpl.java | 8 + .../mapper/LdapSyncDepartmentDetailMapper.xml | 22 + .../resources/mapper/LdapSyncRecordMapper.xml | 32 ++ .../mapper/LdapSyncUserDetailMapper.xml | 25 ++ .../playedu/system/checks/MigrationCheck.java | 81 ++++ 40 files changed, 2588 insertions(+), 39 deletions(-) create mode 100644 .cursor/rules/01-project-overview.mdc create mode 100644 .cursor/rules/02-api-structure.mdc create mode 100644 .cursor/rules/03-configuration.mdc create mode 100644 .cursor/rules/04-module-structure.mdc create mode 100644 .cursor/rules/05-development-workflow.mdc create mode 100644 .cursor/rules/06-security-model.mdc create mode 100644 .cursor/rules/07-database-structure.mdc create mode 100644 playedu-admin/src/api/ldap.ts create mode 100644 playedu-admin/src/pages/department/components/LdapSyncDetailModal.tsx create mode 100644 playedu-admin/src/pages/department/components/LdapSyncItemsModal.tsx create mode 100644 playedu-admin/src/pages/department/components/LdapSyncModal.tsx create mode 100644 playedu-admin/src/pages/department/components/index.ts create mode 100644 playedu-api/docs/ldap-sync-record.md create mode 100644 playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/backend/LdapController.java create mode 100644 playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/backend/LdapSyncDetailController.java create mode 100644 playedu-api/playedu-common/src/main/java/xyz/playedu/common/domain/LdapSyncDepartmentDetail.java create mode 100644 playedu-api/playedu-common/src/main/java/xyz/playedu/common/domain/LdapSyncRecord.java create mode 100644 playedu-api/playedu-common/src/main/java/xyz/playedu/common/domain/LdapSyncUserDetail.java create mode 100644 playedu-api/playedu-common/src/main/java/xyz/playedu/common/mapper/LdapSyncDepartmentDetailMapper.java create mode 100644 playedu-api/playedu-common/src/main/java/xyz/playedu/common/mapper/LdapSyncRecordMapper.java create mode 100644 playedu-api/playedu-common/src/main/java/xyz/playedu/common/mapper/LdapSyncUserDetailMapper.java create mode 100644 playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/LdapSyncDepartmentDetailService.java create mode 100644 playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/LdapSyncRecordService.java create mode 100644 playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/LdapSyncUserDetailService.java create mode 100644 playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/impl/LdapSyncDepartmentDetailServiceImpl.java create mode 100644 playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/impl/LdapSyncRecordServiceImpl.java create mode 100644 playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/impl/LdapSyncUserDetailServiceImpl.java create mode 100644 playedu-api/playedu-common/src/main/resources/mapper/LdapSyncDepartmentDetailMapper.xml create mode 100644 playedu-api/playedu-common/src/main/resources/mapper/LdapSyncRecordMapper.xml create mode 100644 playedu-api/playedu-common/src/main/resources/mapper/LdapSyncUserDetailMapper.xml diff --git a/.cursor/rules/01-project-overview.mdc b/.cursor/rules/01-project-overview.mdc new file mode 100644 index 0000000..d13ed73 --- /dev/null +++ b/.cursor/rules/01-project-overview.mdc @@ -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 diff --git a/.cursor/rules/02-api-structure.mdc b/.cursor/rules/02-api-structure.mdc new file mode 100644 index 0000000..ff5fefa --- /dev/null +++ b/.cursor/rules/02-api-structure.mdc @@ -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 diff --git a/.cursor/rules/03-configuration.mdc b/.cursor/rules/03-configuration.mdc new file mode 100644 index 0000000..c787d97 --- /dev/null +++ b/.cursor/rules/03-configuration.mdc @@ -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 diff --git a/.cursor/rules/04-module-structure.mdc b/.cursor/rules/04-module-structure.mdc new file mode 100644 index 0000000..6bebbf6 --- /dev/null +++ b/.cursor/rules/04-module-structure.mdc @@ -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. diff --git a/.cursor/rules/05-development-workflow.mdc b/.cursor/rules/05-development-workflow.mdc new file mode 100644 index 0000000..9b3138e --- /dev/null +++ b/.cursor/rules/05-development-workflow.mdc @@ -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 diff --git a/.cursor/rules/06-security-model.mdc b/.cursor/rules/06-security-model.mdc new file mode 100644 index 0000000..95d1af9 --- /dev/null +++ b/.cursor/rules/06-security-model.mdc @@ -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 diff --git a/.cursor/rules/07-database-structure.mdc b/.cursor/rules/07-database-structure.mdc new file mode 100644 index 0000000..58f30ca --- /dev/null +++ b/.cursor/rules/07-database-structure.mdc @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a1c8c0..a956cfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ -## 1.9 +## 2.0 +- 新增:`LDAP`同步数据统计 +- 新增:`LDAP`同步的详细记录 +- 新增:`LDAP`同步的数据下载 - 优化:移除`Redis`运行依赖改为使用内存缓存 - 优化:移除本地存储方案`MinIO`的支持改为支持阿里云OSS和腾讯云COS diff --git a/playedu-admin/src/api/index.ts b/playedu-admin/src/api/index.ts index 927f052..1c409eb 100644 --- a/playedu-admin/src/api/index.ts +++ b/playedu-admin/src/api/index.ts @@ -15,3 +15,4 @@ export * as user from "./user"; export * as appConfig from "./app-config"; export * as dashboard from "./dashboard"; export * as adminLog from "./admin-log"; +export * as ldap from "./ldap"; diff --git a/playedu-admin/src/api/ldap.ts b/playedu-admin/src/api/ldap.ts new file mode 100644 index 0000000..3b97f86 --- /dev/null +++ b/playedu-admin/src/api/ldap.ts @@ -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`, {}); +} \ No newline at end of file diff --git a/playedu-admin/src/index.less b/playedu-admin/src/index.less index bad5b83..5a1fffb 100644 --- a/playedu-admin/src/index.less +++ b/playedu-admin/src/index.less @@ -722,3 +722,10 @@ textarea.ant-input { .select-range-modal .ant-tabs-tab + .ant-tabs-tab { margin: 0 0 0 55px; } + +.clickable-stat { + cursor: pointer; +} +.clickable-stat:hover .ant-statistic-content { + color: #1890ff; +} diff --git a/playedu-admin/src/pages/department/components/LdapSyncDetailModal.tsx b/playedu-admin/src/pages/department/components/LdapSyncDetailModal.tsx new file mode 100644 index 0000000..86c2c5f --- /dev/null +++ b/playedu-admin/src/pages/department/components/LdapSyncDetailModal.tsx @@ -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 = ({ + record, + open, + onCancel +}) => { + const [loading, setLoading] = useState(false); + const [detail, setDetail] = useState(null); + const [itemsVisible, setItemsVisible] = useState(false); + const [itemsType, setItemsType] = useState<"department" | "user">("department"); + const [itemsAction, setItemsAction] = useState(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 ( + <> + + {detail && ( + <> + + + + + + + + + + + + + + + + + + + +
showItems("department", 0)} + className="clickable-stat" + > + +
+ + +
showItems("department", 1)} + className="clickable-stat" + > + +
+ + +
showItems("department", 2)} + className="clickable-stat" + > + +
+ + +
showItems("department", 3)} + className="clickable-stat" + > + +
+ +
+
+ + + + + + +
showItems("user", 0)} + className="clickable-stat" + > + +
+ + +
showItems("user", 1)} + className="clickable-stat" + > + +
+ + +
showItems("user", 2)} + className="clickable-stat" + > + +
+ + +
showItems("user", 3)} + className="clickable-stat" + > + +
+ + +
showItems("user", 5)} + className="clickable-stat" + > + +
+ +
+
+ + {detail.error_message && ( + <> + + +
{detail.error_message}
+
+ + )} + + )} +
+ + {detail && ( + setItemsVisible(false)} + /> + )} + + ); +}; \ No newline at end of file diff --git a/playedu-admin/src/pages/department/components/LdapSyncItemsModal.tsx b/playedu-admin/src/pages/department/components/LdapSyncItemsModal.tsx new file mode 100644 index 0000000..8380fe2 --- /dev/null +++ b/playedu-admin/src/pages/department/components/LdapSyncItemsModal.tsx @@ -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 = ({ + recordId, + type, + action, + open, + onCancel +}) => { + const [loading, setLoading] = useState(false); + const [list, setList] = useState([]); + 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) => ( + + {getActionText(actionType)} + + ) + }, + ]; + + 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) => ( + + {getActionText(actionType)} + + ) + }, + ]; + + const columns = type === "department" ? departmentColumns : userColumns; + const title = type === "department" ? "部门同步详情" : "用户同步详情"; + const actionText = action > 0 ? ` - ${getActionText(action)}` : ""; + + return ( + + { + setPage(page); + setSize(pageSize || 10); + }, + showSizeChanger: true, + }} + /> + + ); +}; \ No newline at end of file diff --git a/playedu-admin/src/pages/department/components/LdapSyncModal.tsx b/playedu-admin/src/pages/department/components/LdapSyncModal.tsx new file mode 100644 index 0000000..e42f0e7 --- /dev/null +++ b/playedu-admin/src/pages/department/components/LdapSyncModal.tsx @@ -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 = ({ + open, + onCancel, +}) => { + const [loading, setLoading] = useState(false); + const [syncLoading, setSyncLoading] = useState(false); + const [list, setList] = useState([]); + const [total, setTotal] = useState(0); + const [page, setPage] = useState(1); + const [size, setSize] = useState(10); + const [detailVisible, setDetailVisible] = useState(false); + const [currentRecord, setCurrentRecord] = useState(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 进行中; + if (status === 1) return 成功; + return 失败; + }, + }, + { + 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) => {dateFormat(text)}, + }, + { + title: "操作", + key: "action", + render: (_: any, record: any) => ( +
+ + +
+ ), + }, + ]; + + return ( + <> + +
+ +
+
{ + setPage(page); + setSize(pageSize || 10); + }, + showSizeChanger: true, + }} + /> + + + {currentRecord && ( + setDetailVisible(false)} + /> + )} + + ); +}; diff --git a/playedu-admin/src/pages/department/components/index.ts b/playedu-admin/src/pages/department/components/index.ts new file mode 100644 index 0000000..383a788 --- /dev/null +++ b/playedu-admin/src/pages/department/components/index.ts @@ -0,0 +1,3 @@ +export { LdapSyncModal } from './LdapSyncModal'; +export { LdapSyncDetailModal } from './LdapSyncDetailModal'; +export { LdapSyncItemsModal } from './LdapSyncItemsModal'; \ No newline at end of file diff --git a/playedu-admin/src/pages/department/index.tsx b/playedu-admin/src/pages/department/index.tsx index 17b463b..f26fc5b 100644 --- a/playedu-admin/src/pages/department/index.tsx +++ b/playedu-admin/src/pages/department/index.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react"; import { Spin, Button, Tree, Modal, message, Tooltip } from "antd"; // import styles from "./index.module.less"; import { PlusOutlined, ExclamationCircleFilled } from "@ant-design/icons"; -import { department } from "../../api/index"; +import { department, ldap } from "../../api/index"; import { PerButton } from "../../compenents"; import type { DataNode, TreeProps } from "antd/es/tree"; import { DepartmentCreate } from "./compenents/create"; @@ -10,6 +10,7 @@ import { DepartmentUpdate } from "./compenents/update"; import { useNavigate } from "react-router-dom"; import { useSelector, useDispatch } from "react-redux"; import { saveDepartmentsAction } from "../../store/system/systemConfigSlice"; +import { LdapSyncModal } from "./components"; const { confirm } = Modal; @@ -34,6 +35,7 @@ const DepartmentPage = () => { const [updateVisible, setUpdateVisible] = useState(false); const [did, setDid] = useState(0); const [modal, contextHolder] = Modal.useModal(); + const [syncModalVisible, setSyncModalVisible] = useState(false); // 是否启用LDAP const ldapEnabled = useSelector( @@ -395,16 +397,7 @@ const DepartmentPage = () => { }; const ldapSync = () => { - if (loading) { - message.warning("正在同步,请稍后..."); - return; - } - setLoading(true); - department.ldapSync().then(() => { - message.success("操作成功"); - setLoading(false); - resetData(); - }); + setSyncModalVisible(true); }; return ( @@ -474,6 +467,10 @@ const DepartmentPage = () => { setRefresh(!refresh); }} /> + setSyncModalVisible(false)} + /> ); diff --git a/playedu-api/docs/ldap-sync-record.md b/playedu-api/docs/ldap-sync-record.md new file mode 100644 index 0000000..0056d97 --- /dev/null +++ b/playedu-api/docs/ldap-sync-record.md @@ -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. 更新主同步记录状态为"成功" + +如果同步过程中出现异常,系统会捕获异常信息并更新主同步记录状态为"失败"。 \ No newline at end of file diff --git a/playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/backend/DepartmentController.java b/playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/backend/DepartmentController.java index 518ca35..ccd5468 100644 --- a/playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/backend/DepartmentController.java +++ b/playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/backend/DepartmentController.java @@ -334,8 +334,26 @@ public class DepartmentController { @Log(title = "部门-LDAP同步", businessType = BusinessTypeConstant.INSERT) @SneakyThrows public JsonResponse ldapSync() { - ldapBus.departmentSync(); - ldapBus.userSync(); - return JsonResponse.success(); + try { + // 检查是否启用LDAP + if (!ldapBus.enabledLDAP()) { + return JsonResponse.error("未配置LDAP服务"); + } + + // 检查是否有进行中的同步任务 + if (ldapBus.hasSyncInProgress()) { + return JsonResponse.error("有正在进行的LDAP同步任务,请稍后再试"); + } + + // 使用当前管理员ID执行同步 + Integer recordId = ldapBus.syncAndRecord(BCtx.getId()); + + Map data = new HashMap<>(); + data.put("record_id", recordId); + + return JsonResponse.data(data); + } catch (Exception e) { + return JsonResponse.error("LDAP同步失败: " + e.getMessage()); + } } } diff --git a/playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/backend/LdapController.java b/playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/backend/LdapController.java new file mode 100644 index 0000000..af3e49c --- /dev/null +++ b/playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/backend/LdapController.java @@ -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 result = ldapSyncRecordService.paginate(page, size); + + HashMap 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 data = new HashMap<>(); + data.put("url", url); + + return JsonResponse.data(data); + } catch (Exception e) { + return JsonResponse.error("生成下载链接失败: " + e.getMessage()); + } + } +} diff --git a/playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/backend/LdapSyncDetailController.java b/playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/backend/LdapSyncDetailController.java new file mode 100644 index 0000000..f8ac771 --- /dev/null +++ b/playedu-api/playedu-api/src/main/java/xyz/playedu/api/controller/backend/LdapSyncDetailController.java @@ -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 queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("record_id", id); + if (action > 0) { + queryWrapper.eq("action", action); + } + queryWrapper.orderByDesc("id"); + + IPage pageResult = + ldapSyncDepartmentDetailService.page(new Page<>(page, size), queryWrapper); + + return JsonResponse.data(pageResult); + } else if ("user".equals(type)) { + // 用户同步详情 + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("record_id", id); + if (action > 0) { + queryWrapper.eq("action", action); + } + queryWrapper.orderByDesc("id"); + + IPage pageResult = + ldapSyncUserDetailService.page(new Page<>(page, size), queryWrapper); + + return JsonResponse.data(pageResult); + } + + return JsonResponse.error("不支持的详情类型"); + } +} diff --git a/playedu-api/playedu-api/src/main/java/xyz/playedu/api/schedule/LDAPSchedule.java b/playedu-api/playedu-api/src/main/java/xyz/playedu/api/schedule/LDAPSchedule.java index 23d76cd..a910e7c 100644 --- a/playedu-api/playedu-api/src/main/java/xyz/playedu/api/schedule/LDAPSchedule.java +++ b/playedu-api/playedu-api/src/main/java/xyz/playedu/api/schedule/LDAPSchedule.java @@ -43,17 +43,11 @@ public class LDAPSchedule { } try { - ldapBus.departmentSync(); + // 使用新的同步记录功能 + ldapBus.syncAndRecord(0); // 0表示系统自动执行 + log.info("LDAP同步成功"); } catch (Exception e) { - log.error("LDAP-部门同步失败", e); + log.error("LDAP同步失败", e); } - - try { - ldapBus.userSync(); - } catch (Exception e) { - log.error("LDAP-学员同步失败", e); - } - - log.info("LDAP同步成功"); } } diff --git a/playedu-api/playedu-common/src/main/java/xyz/playedu/common/bus/LDAPBus.java b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/bus/LDAPBus.java index b7943a5..dffa174 100644 --- a/playedu-api/playedu-common/src/main/java/xyz/playedu/common/bus/LDAPBus.java +++ b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/bus/LDAPBus.java @@ -15,7 +15,11 @@ */ package xyz.playedu.common.bus; +import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -26,12 +30,17 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import xyz.playedu.common.domain.Department; import xyz.playedu.common.domain.LdapDepartment; +import xyz.playedu.common.domain.LdapSyncDepartmentDetail; +import xyz.playedu.common.domain.LdapSyncRecord; +import xyz.playedu.common.domain.LdapSyncUserDetail; import xyz.playedu.common.domain.LdapUser; import xyz.playedu.common.domain.User; import xyz.playedu.common.exception.NotFoundException; import xyz.playedu.common.service.*; import xyz.playedu.common.types.LdapConfig; +import xyz.playedu.common.types.config.S3Config; import xyz.playedu.common.util.HelperUtil; +import xyz.playedu.common.util.S3Util; import xyz.playedu.common.util.ldap.LdapTransformDepartment; import xyz.playedu.common.util.ldap.LdapTransformUser; import xyz.playedu.common.util.ldap.LdapUtil; @@ -50,16 +59,381 @@ public class LDAPBus { @Autowired private UserService userService; + @Autowired private LdapSyncRecordService ldapSyncRecordService; + + @Autowired private LdapSyncDepartmentDetailService ldapSyncDepartmentDetailService; + + @Autowired private LdapSyncUserDetailService ldapSyncUserDetailService; + public boolean enabledLDAP() { return appConfigService.enabledLdapLogin(); } - public void departmentSync() throws NamingException, NotFoundException { - LdapConfig ldapConfig = appConfigService.ldapConfig(); + /** 检查是否有进行中的同步任务 */ + public boolean hasSyncInProgress() { + return ldapSyncRecordService.hasSyncInProgress(); + } - List 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 departments = + LdapUtil.departments(ldapConfig, ldapConfig.getBaseDN()); + List users = LdapUtil.users(ldapConfig, ldapConfig.getBaseDN()); + + // 使用查询的数据进行统计 + Map result = collectSyncStatistics(departments, users); + + // 将同步数据保存到S3 + String s3FilePath = saveDataToS3(result, record.getId()); + + // 收集部门和用户的详细同步信息 + List departmentDetails = + collectDepartmentSyncDetails(record.getId(), departments); + List 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 collectDepartmentSyncDetails( + Integer recordId, List departments) throws NotFoundException { + List details = new ArrayList<>(); + Date now = new Date(); + + // 读取已经同步的记录 + Map 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 uuidList = departments.stream().map(LdapTransformDepartment::getUuid).toList(); + List 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 collectUserSyncDetails( + Integer recordId, List users) { + List 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 uuidList = + users.stream().filter(u -> !u.isBan()).map(LdapTransformUser::getId).toList(); + + // 获取所有现有的LDAP用户记录 + List allLdapUsers = ldapUserService.list(); + + // 过滤出不在当前LDAP用户列表中的用户 + List 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 collectSyncStatistics( + List departments, List users) { + Map result = new HashMap<>(); + Map 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 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 uuidList = + departments.stream().map(LdapTransformDepartment::getUuid).toList(); + List 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 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 ouList) throws NotFoundException { if (ouList == null || ouList.isEmpty()) { return; } @@ -203,11 +577,12 @@ public class LDAPBus { } } - public void userSync() throws NamingException, IOException { - LdapConfig ldapConfig = appConfigService.ldapConfig(); - - List userList = LdapUtil.users(ldapConfig, ldapConfig.getBaseDN()); - + /** + * 执行用户同步 - 提供现有的LDAP用户数据 + * + * @param userList 已获取的LDAP用户数据 + */ + public void userSync(List userList) { if (userList == null || userList.isEmpty()) { return; } @@ -216,12 +591,18 @@ public class LDAPBus { for (LdapTransformUser ldapTransformUser : userList) { if (ldapTransformUser.isBan()) { - log.info( - "LDAP-用户同步-用户被禁止|ctx=[dn:{},uuid={}]", - ldapTransformUser.getDn(), - ldapTransformUser.getId()); - continue; + // 检查用户是否已在系统中存在 + LdapUser existingLdapUser = ldapUserService.findByUUID(ldapTransformUser.getId()); + if (existingLdapUser == null) { + // 对于新的被禁止用户,不同步到系统 + log.info( + "LDAP-用户同步-新用户被禁止不同步|ctx=[dn:{},uuid={}]", + ldapTransformUser.getDn(), + ldapTransformUser.getId()); + continue; + } } + singleUserSync(ldapTransformUser, defaultAvatar); } } @@ -326,6 +707,15 @@ public class LDAPBus { if (!newOU.equals(ldapUser.getOu())) { userService.updateDepId(user.getId(), depIds); ldapUserService.updateOU(ldapUser.getId(), newOU); + + if (ldapTransformUser.isBan()) { + log.info("LDAP-用户同步-被禁止用户部门已更新|ctx=[userId:{},新OU:{}]", user.getId(), newOU); + } + } + + // DN变化 + if (!ldapTransformUser.getDn().equals(ldapUser.getDn())) { + ldapUserService.updateDN(ldapUser.getId(), ldapTransformUser.getDn()); } } diff --git a/playedu-api/playedu-common/src/main/java/xyz/playedu/common/domain/LdapSyncDepartmentDetail.java b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/domain/LdapSyncDepartmentDetail.java new file mode 100644 index 0000000..44d6768 --- /dev/null +++ b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/domain/LdapSyncDepartmentDetail.java @@ -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; +} diff --git a/playedu-api/playedu-common/src/main/java/xyz/playedu/common/domain/LdapSyncRecord.java b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/domain/LdapSyncRecord.java new file mode 100644 index 0000000..571819b --- /dev/null +++ b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/domain/LdapSyncRecord.java @@ -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; +} diff --git a/playedu-api/playedu-common/src/main/java/xyz/playedu/common/domain/LdapSyncUserDetail.java b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/domain/LdapSyncUserDetail.java new file mode 100644 index 0000000..e5bff32 --- /dev/null +++ b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/domain/LdapSyncUserDetail.java @@ -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; +} diff --git a/playedu-api/playedu-common/src/main/java/xyz/playedu/common/mapper/LdapSyncDepartmentDetailMapper.java b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/mapper/LdapSyncDepartmentDetailMapper.java new file mode 100644 index 0000000..980345e --- /dev/null +++ b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/mapper/LdapSyncDepartmentDetailMapper.java @@ -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 {} diff --git a/playedu-api/playedu-common/src/main/java/xyz/playedu/common/mapper/LdapSyncRecordMapper.java b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/mapper/LdapSyncRecordMapper.java new file mode 100644 index 0000000..d654652 --- /dev/null +++ b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/mapper/LdapSyncRecordMapper.java @@ -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 {} diff --git a/playedu-api/playedu-common/src/main/java/xyz/playedu/common/mapper/LdapSyncUserDetailMapper.java b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/mapper/LdapSyncUserDetailMapper.java new file mode 100644 index 0000000..a25bb16 --- /dev/null +++ b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/mapper/LdapSyncUserDetailMapper.java @@ -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 {} diff --git a/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/LdapSyncDepartmentDetailService.java b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/LdapSyncDepartmentDetailService.java new file mode 100644 index 0000000..9fe6aac --- /dev/null +++ b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/LdapSyncDepartmentDetailService.java @@ -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 { + + /** + * 批量创建部门同步详情记录 + * + * @param details 部门同步详情记录列表 + */ + void batchCreate(List details); + + /** + * 根据同步记录ID和操作类型获取部门同步详情 + * + * @param recordId 同步记录ID + * @param action 操作类型,1-新增,2-更新,3-删除,4-无变化 + * @return 部门同步详情列表 + */ + List getByRecordIdAndAction(Integer recordId, Integer action); +} diff --git a/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/LdapSyncRecordService.java b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/LdapSyncRecordService.java new file mode 100644 index 0000000..1ef4714 --- /dev/null +++ b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/LdapSyncRecordService.java @@ -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 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 paginate(Integer page, Integer size); + + // 检查是否有进行中的同步任务 + boolean hasSyncInProgress(); + + // 获取最近一次同步记录 + LdapSyncRecord getLatestRecord(); +} diff --git a/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/LdapSyncUserDetailService.java b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/LdapSyncUserDetailService.java new file mode 100644 index 0000000..ee5cd8f --- /dev/null +++ b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/LdapSyncUserDetailService.java @@ -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 { + + /** + * 批量创建用户同步详情记录 + * + * @param details 用户同步详情记录列表 + */ + void batchCreate(List details); + + /** + * 根据同步记录ID和操作类型获取用户同步详情 + * + * @param recordId 同步记录ID + * @param action 操作类型,1-新增,2-更新,3-删除,4-无变化 + * @return 用户同步详情列表 + */ + List getByRecordIdAndAction(Integer recordId, Integer action); +} diff --git a/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/LdapUserService.java b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/LdapUserService.java index bddd13a..063d229 100644 --- a/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/LdapUserService.java +++ b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/LdapUserService.java @@ -38,4 +38,6 @@ public interface LdapUserService extends IService { void updateEmail(Integer id, String email); void updateUid(Integer id, String uid); + + void updateDN(Integer id, String dn); } diff --git a/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/impl/LdapSyncDepartmentDetailServiceImpl.java b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/impl/LdapSyncDepartmentDetailServiceImpl.java new file mode 100644 index 0000000..55440b0 --- /dev/null +++ b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/impl/LdapSyncDepartmentDetailServiceImpl.java @@ -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 + implements LdapSyncDepartmentDetailService { + + @Override + public void batchCreate(List details) { + if (details == null || details.isEmpty()) { + return; + } + saveBatch(details); + } + + @Override + public List getByRecordIdAndAction(Integer recordId, Integer action) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("record_id", recordId); + if (action > 0) { + queryWrapper.eq("action", action); + } + queryWrapper.orderByDesc("id"); + return list(queryWrapper); + } +} diff --git a/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/impl/LdapSyncRecordServiceImpl.java b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/impl/LdapSyncRecordServiceImpl.java new file mode 100644 index 0000000..43f06aa --- /dev/null +++ b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/impl/LdapSyncRecordServiceImpl.java @@ -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 + 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 paginate(Integer page, Integer size) { + Page pageObj = new Page<>(page, size); + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.orderByDesc("id"); + + IPage iPage = page(pageObj, wrapper); + + PaginationResult result = new PaginationResult<>(); + result.setData(iPage.getRecords()); + result.setTotal(iPage.getTotal()); + + return result; + } + + @Override + public boolean hasSyncInProgress() { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("status", 0); + return count(wrapper) > 0; + } + + @Override + public LdapSyncRecord getLatestRecord() { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.orderByDesc("id"); + wrapper.last("limit 1"); + return getOne(wrapper); + } +} diff --git a/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/impl/LdapSyncUserDetailServiceImpl.java b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/impl/LdapSyncUserDetailServiceImpl.java new file mode 100644 index 0000000..1800c64 --- /dev/null +++ b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/impl/LdapSyncUserDetailServiceImpl.java @@ -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 + implements LdapSyncUserDetailService { + + @Override + public void batchCreate(List details) { + if (details == null || details.isEmpty()) { + return; + } + saveBatch(details); + } + + @Override + public List getByRecordIdAndAction(Integer recordId, Integer action) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("record_id", recordId); + if (action > 0) { + queryWrapper.eq("action", action); + } + queryWrapper.orderByDesc("id"); + return list(queryWrapper); + } +} diff --git a/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/impl/LdapUserServiceImpl.java b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/impl/LdapUserServiceImpl.java index db7542d..1296094 100644 --- a/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/impl/LdapUserServiceImpl.java +++ b/playedu-api/playedu-common/src/main/java/xyz/playedu/common/service/impl/LdapUserServiceImpl.java @@ -98,4 +98,12 @@ public class LdapUserServiceImpl extends ServiceImpl user.setUid(uid); updateById(user); } + + @Override + public void updateDN(Integer id, String dn) { + LdapUser user = new LdapUser(); + user.setId(id); + user.setDn(dn); + updateById(user); + } } diff --git a/playedu-api/playedu-common/src/main/resources/mapper/LdapSyncDepartmentDetailMapper.xml b/playedu-api/playedu-common/src/main/resources/mapper/LdapSyncDepartmentDetailMapper.xml new file mode 100644 index 0000000..19192a0 --- /dev/null +++ b/playedu-api/playedu-common/src/main/resources/mapper/LdapSyncDepartmentDetailMapper.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + id, record_id, department_id, uuid, dn, name, action, created_at + + + \ No newline at end of file diff --git a/playedu-api/playedu-common/src/main/resources/mapper/LdapSyncRecordMapper.xml b/playedu-api/playedu-common/src/main/resources/mapper/LdapSyncRecordMapper.xml new file mode 100644 index 0000000..c746920 --- /dev/null +++ b/playedu-api/playedu-common/src/main/resources/mapper/LdapSyncRecordMapper.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + \ No newline at end of file diff --git a/playedu-api/playedu-common/src/main/resources/mapper/LdapSyncUserDetailMapper.xml b/playedu-api/playedu-common/src/main/resources/mapper/LdapSyncUserDetailMapper.xml new file mode 100644 index 0000000..fe153fd --- /dev/null +++ b/playedu-api/playedu-common/src/main/resources/mapper/LdapSyncUserDetailMapper.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + id, record_id, user_id, uuid, dn, cn, uid, email, ou, action, created_at + + + \ No newline at end of file diff --git a/playedu-api/playedu-system/src/main/java/xyz/playedu/system/checks/MigrationCheck.java b/playedu-api/playedu-system/src/main/java/xyz/playedu/system/checks/MigrationCheck.java index 90691bf..cf96bdd 100644 --- a/playedu-api/playedu-system/src/main/java/xyz/playedu/system/checks/MigrationCheck.java +++ b/playedu-api/playedu-system/src/main/java/xyz/playedu/system/checks/MigrationCheck.java @@ -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用户同步详情表'; + """); + } + }); } };