From 248403b094eabf94d6fb0de64871b47e6f4d59b9 Mon Sep 17 00:00:00 2001 From: Marsway Date: Tue, 24 Mar 2026 18:07:31 +0800 Subject: [PATCH] update --- ad_user_creator/ldap_client.py | 30 +++++++++++++++++++++++++++++- state/run.log | 1 + state/uid_state.json | 4 ++-- state/web.log | 0 4 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 state/web.log diff --git a/ad_user_creator/ldap_client.py b/ad_user_creator/ldap_client.py index 9c4052a..eda965e 100644 --- a/ad_user_creator/ldap_client.py +++ b/ad_user_creator/ldap_client.py @@ -1,15 +1,31 @@ from __future__ import annotations +from functools import wraps from typing import Dict, Optional from ldap3 import ALL, BASE, Connection, MODIFY_REPLACE, Server -from ldap3.core.exceptions import LDAPException +from ldap3.core.exceptions import LDAPException, LDAPCommunicationError from ldap3.utils.conv import escape_filter_chars from ad_user_creator.exceptions import LdapConnectionError, LdapOperationError from ad_user_creator.models import LdapConfig +def _with_auto_reconnect(func): + """ + 捕获底层 LDAP 网络异常并自动重连重试一次。 + 适用于使用长连接时防止因网络波动或闲置导致的 EOF 错误。 + """ + @wraps(func) + def wrapper(self: "LdapClient", *args, **kwargs): + try: + return func(self, *args, **kwargs) + except LDAPCommunicationError: + self.connect() + return func(self, *args, **kwargs) + return wrapper + + class LdapClient: def __init__(self, config: LdapConfig) -> None: self.config = config @@ -36,6 +52,7 @@ class LdapClient: if self.conn is not None and self.conn.bound: self.conn.unbind() + @_with_auto_reconnect def user_exists(self, sam_account_name: str) -> bool: self.ensure_connected() assert self.conn is not None @@ -46,6 +63,7 @@ class LdapClient: return False return len(self.conn.entries) > 0 + @_with_auto_reconnect def find_user_dn_by_sam(self, sam_account_name: str) -> Optional[str]: self.ensure_connected() assert self.conn is not None @@ -56,6 +74,7 @@ class LdapClient: return None return str(self.conn.entries[0].distinguishedName.value) + @_with_auto_reconnect def group_exists(self, group_name: str) -> bool: self.ensure_connected() assert self.conn is not None @@ -66,6 +85,7 @@ class LdapClient: return False return len(self.conn.entries) > 0 + @_with_auto_reconnect def get_group_dn(self, group_name: str) -> str: self.ensure_connected() assert self.conn is not None @@ -76,6 +96,7 @@ class LdapClient: raise LdapOperationError(f"组不存在: {group_name}") return str(self.conn.entries[0].distinguishedName.value) + @_with_auto_reconnect def get_group_gid_number(self, group_name: str) -> Optional[int]: self.ensure_connected() assert self.conn is not None @@ -95,6 +116,7 @@ class LdapClient: except (TypeError, ValueError): return None + @_with_auto_reconnect def create_user(self, user_dn: str, attributes: Dict[str, object]) -> None: self.ensure_connected() assert self.conn is not None @@ -102,6 +124,7 @@ class LdapClient: if not ok: raise LdapOperationError(f"创建用户失败: {self.conn.result}") + @_with_auto_reconnect def set_user_password(self, user_dn: str, new_password: str) -> None: self.ensure_connected() assert self.conn is not None @@ -109,6 +132,7 @@ class LdapClient: if not ok: raise LdapOperationError(f"设置用户密码失败 user={user_dn} result={self.conn.result}") + @_with_auto_reconnect def set_user_enabled(self, user_dn: str, enabled: bool) -> None: self.ensure_connected() assert self.conn is not None @@ -118,6 +142,7 @@ class LdapClient: action = "启用" if enabled else "禁用" raise LdapOperationError(f"{action}用户失败 user={user_dn} result={self.conn.result}") + @_with_auto_reconnect def add_user_to_group(self, user_dn: str, group_dn: str) -> None: self.ensure_connected() assert self.conn is not None @@ -127,6 +152,7 @@ class LdapClient: f"添加组成员失败 user={user_dn} group={group_dn} result={self.conn.result}" ) + @_with_auto_reconnect def get_user_attributes(self, user_dn: str, attrs: list[str]) -> Dict[str, str]: self.ensure_connected() assert self.conn is not None @@ -148,6 +174,7 @@ class LdapClient: result[attr] = "" return result + @_with_auto_reconnect def modify_user_attributes(self, user_dn: str, changes: Dict[str, str]) -> None: self.ensure_connected() assert self.conn is not None @@ -156,6 +183,7 @@ class LdapClient: if not ok: raise LdapOperationError(f"更新用户属性失败 user={user_dn} result={self.conn.result}") + @_with_auto_reconnect def add_user_to_group_if_missing(self, user_dn: str, group_dn: str) -> bool: self.ensure_connected() assert self.conn is not None diff --git a/state/run.log b/state/run.log index 5c8f256..980e82c 100644 --- a/state/run.log +++ b/state/run.log @@ -301,3 +301,4 @@ 2026-03-17 10:44:31,846 [INFO] 配置加载完成 2026-03-19 23:46:47,952 [INFO] 配置加载完成 2026-03-24 12:16:13,498 [INFO] 配置加载完成 +2026-03-24 17:57:59,426 [INFO] 配置加载完成 diff --git a/state/uid_state.json b/state/uid_state.json index 071bfcf..6e29f2e 100644 --- a/state/uid_state.json +++ b/state/uid_state.json @@ -1,4 +1,4 @@ { - "next_uid_number": 2132, - "updated_at": "2026-03-17T02:44:35+00:00" + "next_uid_number": 2133, + "updated_at": "2026-03-24T09:58:17+00:00" } \ No newline at end of file diff --git a/state/web.log b/state/web.log new file mode 100644 index 0000000..e69de29