원클릭으로
ad-employee-sync
Полный цикл синхронизации справочника из Active Directory — сброс структуры, сохранение порядка, блокирующий код в WebFlux
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Полный цикл синхронизации справочника из Active Directory — сброс структуры, сохранение порядка, блокирующий код в WebFlux
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Динамический LdapTemplate из БД + шифрование пароля + отключение авто-LDAP для безопасного старта без AD
Экспорт Excel с фильтром по датам — использование Thymeleaf inline для передачи дат из формы в JS
Feature-флаг в БД (app_settings) с UI-тумблером на странице профиля
Парсинг Distinguished Name из Active Directory, извлечение OU-цепочки, правила маппинга на Department/Division и блокирующие вызовы в WebFlux
Разрешение конфликта Spring Data LDAP + R2DBC — strict mode, @Table на все entity, отключение LDAP-репозиториев
Подключение Spring Boot к Active Directory через LdapTemplate — зависимости, конфигурация, поиск, SearchControls для бинарных атрибутов
| name | ad-employee-sync |
| description | Полный цикл синхронизации справочника из Active Directory — сброс структуры, сохранение порядка, блокирующий код в WebFlux |
| source | auto-skill |
| extracted_at | 2026-06-11T20:21:09.448Z |
Полный цикл синхронизации сотрудников и структуры из Active Directory в локальную БД.
@Scheduled (раз в час) ──┐
├── LdapSyncService.syncAll()
POST /api/admin/sync-directory ──┘ │
├── 1. Сохранить порядок (Map<title→number>)
├── 2. Открепить всех сотрудников
├── 3. Удалить все Department + Division
├── 4. Загрузить пользователей из AD
├── 5. Для каждого: найти/создать dept+div, создать/обновить employee
├── 6. Открепить тех, кого нет в AD
└── 7. INACTIVE в employee_job_system для откреплённых
@PostMapping("/admin/sync-directory")
@PreAuthorize("hasRole('ADMIN')")
public Mono<Map<String, Object>> syncDirectory() {
return Mono.fromCallable(() -> ldapSyncService.syncAll())
.subscribeOn(Schedulers.boundedElastic());
}
.block() внутри syncAll() разрешён только в boundedElastic-потоке.
Map<String, Integer> deptOrder = new HashMap<>(); // title(lower) → number
Map<String, Integer> divOrder = new HashMap<>(); // title(lower)|deptId → number
for (Department d : oldDepts) deptOrder.put(d.getTitle().toLowerCase(), d.getNumber());
for (Division d : oldDivs) divOrder.put(d.getTitle().toLowerCase() + "|" + d.getDepartmentId(), d.getNumber());
Department d = new Department();
Integer oldNum = deptOrder.get(title.toLowerCase());
d.setNumber(oldNum != null ? oldNum : maxNum + 1);
databaseClient.sql("UPDATE employee_job_system SET status = 'INACTIVE', disconnect_date = :now " +
"WHERE employee_id = :empId AND status = 'ACTIVE'")
.bind("empId", emp.getId())
.bind("now", LocalDate.now())
.fetch().rowsUpdated().subscribe();
objectGUID ненадёжен в JNDI (возвращается строкой, теряются байты). Использовать email с equalsIgnoreCase.
String[] parts = cn.trim().split("\\s+"); // "Карачков Иван Сергеевич"
lastname = normalize(parts[0]); // Карачков
name = normalize(parts[1]); // Иван
middleName = parts.length > 2 ? normalize(parts[2]) : "—";
<div class="profile-card__footer" th:if="${admin}">
<button class="btn btn-smoke w-100" onclick="syncDirectory()">
<i class="bi bi-arrow-repeat"></i> Синхронизировать с AD
</button>
</div>
CSRF-токен обязателен в fetch-запросе (читать из <meta name='_csrf'>).
@EnableScheduling // на @SpringBootApplication
@Scheduled(cron = "0 0 * * * *") // раз в час
public void scheduledSync() { syncAll(); }
ldap-dn-parsing-ou-hierarchy — парсинг DN и маппинг OUspring-ldap-integration — LdapTemplate, фильтры, конфигурацияr2dbc-database-client-cud — DatabaseClient для CUDwebflux-preauthorize-reactive-return — Mono обёртка для @PreAuthorizeapp-settings-feature-toggle — тумблер включения/выключения фичи из БД