sycommon-python-lib 0.2.6a0__py3-none-any.whl → 0.2.6a2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sycommon/auth/wecom_ldap_service.py +149 -16
- {sycommon_python_lib-0.2.6a0.dist-info → sycommon_python_lib-0.2.6a2.dist-info}/METADATA +1 -1
- {sycommon_python_lib-0.2.6a0.dist-info → sycommon_python_lib-0.2.6a2.dist-info}/RECORD +6 -6
- {sycommon_python_lib-0.2.6a0.dist-info → sycommon_python_lib-0.2.6a2.dist-info}/WHEEL +0 -0
- {sycommon_python_lib-0.2.6a0.dist-info → sycommon_python_lib-0.2.6a2.dist-info}/entry_points.txt +0 -0
- {sycommon_python_lib-0.2.6a0.dist-info → sycommon_python_lib-0.2.6a2.dist-info}/top_level.txt +0 -0
|
@@ -73,7 +73,8 @@ class LDAPFullUser(BaseModel):
|
|
|
73
73
|
class WeComLDAPUser(BaseModel):
|
|
74
74
|
"""企微-LDAP 关联用户(最终返回结果)"""
|
|
75
75
|
|
|
76
|
-
wecom_userid: str # 企微 userid
|
|
76
|
+
wecom_userid: str # 企微 userid(明文工号,如 "0633")
|
|
77
|
+
employee_id: Optional[str] = None # 员工工号(与 wecom_userid 同值,语义更明确)
|
|
77
78
|
wecom_name: str # 企微中文名
|
|
78
79
|
wecom_english_name: Optional[str] = None # 企微英文名
|
|
79
80
|
wecom_departments: List[str] = [] # 企微部门列表
|
|
@@ -270,24 +271,19 @@ class WeComLDAPService(metaclass=SingletonMeta):
|
|
|
270
271
|
async def _get_bot_access_token(self) -> str:
|
|
271
272
|
"""获取用于 open_userid 解码的 access_token
|
|
272
273
|
|
|
273
|
-
|
|
274
|
-
|
|
274
|
+
始终使用 WeComDecodeUserID 配置的凭据。
|
|
275
|
+
WeCom 通讯录 token 用于查用户/部门;
|
|
276
|
+
WeComDecodeUserID token 专用于 batch/openuserid_to_userid 解码。
|
|
275
277
|
"""
|
|
276
|
-
import os
|
|
277
278
|
from sycommon.config.Config import Config
|
|
278
279
|
|
|
279
|
-
is_dev = os.environ.get("IS_DEV") == "true"
|
|
280
280
|
config = Config().config.get("llm", {})
|
|
281
|
-
|
|
282
|
-
if is_dev:
|
|
283
|
-
wecom_config = config.get("WeComDecodeUserID", {}) or config.get("WeCom", {})
|
|
284
|
-
else:
|
|
285
|
-
wecom_config = config.get("WeCom", {})
|
|
281
|
+
wecom_config = config.get("WeComDecodeUserID", {})
|
|
286
282
|
|
|
287
283
|
corpid = wecom_config.get("corpid", "")
|
|
288
284
|
corpsecret = wecom_config.get("corpsecret", "")
|
|
289
285
|
if not corpid or not corpsecret:
|
|
290
|
-
raise ValueError("解码凭据配置不完整,需要配置 llm.WeComDecodeUserID
|
|
286
|
+
raise ValueError("解码凭据配置不完整,需要配置 llm.WeComDecodeUserID")
|
|
291
287
|
|
|
292
288
|
url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken"
|
|
293
289
|
params = {"corpid": corpid, "corpsecret": corpsecret}
|
|
@@ -374,10 +370,11 @@ class WeComLDAPService(metaclass=SingletonMeta):
|
|
|
374
370
|
async def _get_wecom_user(self, userid: str) -> Optional[dict]:
|
|
375
371
|
"""获取单个企微用户详情
|
|
376
372
|
|
|
377
|
-
|
|
378
|
-
|
|
373
|
+
始终使用 WeCom 通讯录 token 查询用户详情。
|
|
374
|
+
注意:WeComDecodeUserID token 只能用于 open_userid 解码,
|
|
375
|
+
不能查 /cgi-bin/user/get(会返回 errcode 60111)。
|
|
379
376
|
"""
|
|
380
|
-
token = await self.
|
|
377
|
+
token = await self._get_access_token()
|
|
381
378
|
url = f"https://qyapi.weixin.qq.com/cgi-bin/user/get"
|
|
382
379
|
async with aiohttp.ClientSession() as session:
|
|
383
380
|
async with session.get(url, params={"access_token": token, "userid": userid}) as resp:
|
|
@@ -603,8 +600,9 @@ class WeComLDAPService(metaclass=SingletonMeta):
|
|
|
603
600
|
|
|
604
601
|
# 3. 合并返回
|
|
605
602
|
result = await self._build_wecom_ldap_user(user_data, ldap_index)
|
|
606
|
-
#
|
|
607
|
-
result.wecom_userid =
|
|
603
|
+
# 保留解码后的明文 userid(工号),而非原始密文
|
|
604
|
+
result.wecom_userid = decoded_userid
|
|
605
|
+
result.employee_id = decoded_userid
|
|
608
606
|
return result
|
|
609
607
|
|
|
610
608
|
async def get_all_users(self) -> List[WeComLDAPUser]:
|
|
@@ -623,6 +621,7 @@ class WeComLDAPService(metaclass=SingletonMeta):
|
|
|
623
621
|
results = []
|
|
624
622
|
for u in wecom_users:
|
|
625
623
|
result = await self._build_wecom_ldap_user(u, ldap_index)
|
|
624
|
+
result.employee_id = result.wecom_userid
|
|
626
625
|
results.append(result)
|
|
627
626
|
|
|
628
627
|
matched = sum(1 for r in results if r.matched)
|
|
@@ -671,3 +670,137 @@ class WeComLDAPService(metaclass=SingletonMeta):
|
|
|
671
670
|
if user.matched and user.ldap_user:
|
|
672
671
|
return user.ldap_user.username
|
|
673
672
|
return None
|
|
673
|
+
|
|
674
|
+
# ─── Redis 映射缓存 ───
|
|
675
|
+
|
|
676
|
+
# Redis key 前缀(保持与旧版 digital_work 命名兼容)
|
|
677
|
+
_UID_MAP_PREFIX = "digital_work:wecom_uid_map:"
|
|
678
|
+
_LDAP_TO_WECOM_PREFIX = "digital_work:ldap_to_wecom:"
|
|
679
|
+
_EMPLOYEE_ID_PREFIX = "digital_work:wecom_employee_id:"
|
|
680
|
+
_MAP_TTL = 7 * 24 * 3600 # 7 天
|
|
681
|
+
|
|
682
|
+
@staticmethod
|
|
683
|
+
def _get_redis():
|
|
684
|
+
"""获取 RedisService(懒加载)"""
|
|
685
|
+
from sycommon.database.redis_service import RedisService
|
|
686
|
+
return RedisService
|
|
687
|
+
|
|
688
|
+
async def _get_cached_mapping(self, wecom_userid: str) -> Optional[str]:
|
|
689
|
+
"""从 Redis 读取 wecom_userid -> unified_id 映射"""
|
|
690
|
+
try:
|
|
691
|
+
rs = self._get_redis()
|
|
692
|
+
val = await rs.get(f"{self._UID_MAP_PREFIX}{wecom_userid}")
|
|
693
|
+
if val:
|
|
694
|
+
return val
|
|
695
|
+
except Exception:
|
|
696
|
+
pass
|
|
697
|
+
return None
|
|
698
|
+
|
|
699
|
+
async def _set_cached_mapping(self, wecom_userid: str, unified_id: str):
|
|
700
|
+
"""将 wecom_userid <-> unified_id 双向映射写入 Redis,同时保存工号"""
|
|
701
|
+
try:
|
|
702
|
+
rs = self._get_redis()
|
|
703
|
+
# 正向: wecom_userid -> unified_id (LDAP sAMAccountName)
|
|
704
|
+
await rs.set(
|
|
705
|
+
f"{self._UID_MAP_PREFIX}{wecom_userid}",
|
|
706
|
+
unified_id,
|
|
707
|
+
ex=self._MAP_TTL,
|
|
708
|
+
)
|
|
709
|
+
# 反向: unified_id -> wecom_userid(用于定时任务等主动推送场景)
|
|
710
|
+
await rs.set(
|
|
711
|
+
f"{self._LDAP_TO_WECOM_PREFIX}{unified_id}",
|
|
712
|
+
wecom_userid,
|
|
713
|
+
ex=self._MAP_TTL,
|
|
714
|
+
)
|
|
715
|
+
# 员工工号: unified_id -> wecom_userid(即工号,如 "0633")
|
|
716
|
+
await rs.set(
|
|
717
|
+
f"{self._EMPLOYEE_ID_PREFIX}{unified_id}",
|
|
718
|
+
wecom_userid,
|
|
719
|
+
ex=self._MAP_TTL,
|
|
720
|
+
)
|
|
721
|
+
except Exception as e:
|
|
722
|
+
SYLogger.warning(f"[WeComLDAP] 缓存映射到 Redis 失败: {e}")
|
|
723
|
+
|
|
724
|
+
async def resolve_unified_user_id(self, wecom_userid: str) -> str:
|
|
725
|
+
"""将企微 userid 解析为统一 user_id(LDAP sAMAccountName)
|
|
726
|
+
|
|
727
|
+
优先从 Redis 缓存读取,缓存未命中时查 LDAP 并写入 Redis。
|
|
728
|
+
匹配到 LDAP 返回 sAMAccountName,否则直接使用企微 userid。
|
|
729
|
+
|
|
730
|
+
Args:
|
|
731
|
+
wecom_userid: 企微用户ID(如 "0633"、密文 open_userid)
|
|
732
|
+
|
|
733
|
+
Returns:
|
|
734
|
+
统一 user_id(小写,如 "osulcode.xiao")
|
|
735
|
+
"""
|
|
736
|
+
# 1. 查 Redis 缓存
|
|
737
|
+
cached = await self._get_cached_mapping(wecom_userid)
|
|
738
|
+
if cached:
|
|
739
|
+
return cached
|
|
740
|
+
|
|
741
|
+
# 2. 查企微 + LDAP
|
|
742
|
+
unified_id = wecom_userid
|
|
743
|
+
employee_id = wecom_userid # 默认用工号就是原始 userid
|
|
744
|
+
try:
|
|
745
|
+
result = await self.get_user_info(wecom_userid)
|
|
746
|
+
employee_id = result.wecom_userid # get_user_info 返回解码后的明文工号
|
|
747
|
+
if result.matched and result.ldap_user and result.ldap_user.username:
|
|
748
|
+
unified_id = result.ldap_user.username
|
|
749
|
+
SYLogger.info(
|
|
750
|
+
f"[WeComLDAP] 用户映射: wecom={wecom_userid} -> ldap={unified_id}")
|
|
751
|
+
else:
|
|
752
|
+
SYLogger.info(
|
|
753
|
+
f"[WeComLDAP] 用户映射: wecom={wecom_userid} -> 未匹配LDAP,回退={unified_id}")
|
|
754
|
+
except Exception as e:
|
|
755
|
+
SYLogger.warning(
|
|
756
|
+
f"[WeComLDAP] 解析统一 user_id 失败: wecom_userid={wecom_userid}, error={e}")
|
|
757
|
+
|
|
758
|
+
# 3. 写入 Redis 缓存(统一小写)
|
|
759
|
+
unified_id = unified_id.lower().replace(" ", "")
|
|
760
|
+
await self._set_cached_mapping(employee_id, unified_id)
|
|
761
|
+
|
|
762
|
+
return unified_id
|
|
763
|
+
|
|
764
|
+
async def get_employee_id(self, unified_id: str) -> Optional[str]:
|
|
765
|
+
"""从 Redis 获取企微员工工号
|
|
766
|
+
|
|
767
|
+
Args:
|
|
768
|
+
unified_id: 统一用户 ID(LDAP sAMAccountName,如 "osulcode.xiao")
|
|
769
|
+
|
|
770
|
+
Returns:
|
|
771
|
+
员工工号(如 "0633"),未找到返回 None
|
|
772
|
+
"""
|
|
773
|
+
try:
|
|
774
|
+
rs = self._get_redis()
|
|
775
|
+
uid = unified_id.lower().replace(" ", "")
|
|
776
|
+
val = await rs.get(f"{self._EMPLOYEE_ID_PREFIX}{uid}")
|
|
777
|
+
if val:
|
|
778
|
+
return val
|
|
779
|
+
except Exception:
|
|
780
|
+
pass
|
|
781
|
+
return None
|
|
782
|
+
|
|
783
|
+
async def batch_set_uid_mapping(self, mapping: dict[str, str]):
|
|
784
|
+
"""批量写入企微-LDAP 映射到 Redis(pipeline 优化,含反向映射和工号)
|
|
785
|
+
|
|
786
|
+
Args:
|
|
787
|
+
mapping: {wecom_userid: unified_id, ...}
|
|
788
|
+
"""
|
|
789
|
+
if not mapping:
|
|
790
|
+
return
|
|
791
|
+
try:
|
|
792
|
+
rs = self._get_redis()
|
|
793
|
+
client = rs.get_client()
|
|
794
|
+
async with client.pipeline(transaction=True) as pipe:
|
|
795
|
+
for wecom_userid, unified_id in mapping.items():
|
|
796
|
+
uid = unified_id.lower().replace(" ", "")
|
|
797
|
+
# 正向
|
|
798
|
+
pipe.set(f"{self._UID_MAP_PREFIX}{wecom_userid}", uid, ex=self._MAP_TTL)
|
|
799
|
+
# 反向
|
|
800
|
+
pipe.set(f"{self._LDAP_TO_WECOM_PREFIX}{uid}", wecom_userid, ex=self._MAP_TTL)
|
|
801
|
+
# 员工工号
|
|
802
|
+
pipe.set(f"{self._EMPLOYEE_ID_PREFIX}{uid}", wecom_userid, ex=self._MAP_TTL)
|
|
803
|
+
await pipe.execute()
|
|
804
|
+
logging.info(f"[WeComLDAP] 批量写入 {len(mapping)} 条映射到 Redis")
|
|
805
|
+
except Exception as e:
|
|
806
|
+
SYLogger.warning(f"[WeComLDAP] 批量缓存映射到 Redis 失败: {e}")
|
|
@@ -147,7 +147,7 @@ sycommon/auth/ldap_service.py,sha256=fOcpVov5LWJkBk62qbTaltks1c4la7JsbD104KfdBOI
|
|
|
147
147
|
sycommon/auth/oa_cache.py,sha256=u673y-mK-xo24pSqvfjL68_YFASO2zoxd_iAQ0HetCo,3491
|
|
148
148
|
sycommon/auth/oa_crypto.py,sha256=xpY1R1Bj3KLENXB0TuThB6Eku1E9PYjcoSpOdgDmCgc,1898
|
|
149
149
|
sycommon/auth/oa_service.py,sha256=kLepV9zgqpZoaB73DRPpMA5tJJQjoaDtQPdzBcGXeak,6235
|
|
150
|
-
sycommon/auth/wecom_ldap_service.py,sha256=
|
|
150
|
+
sycommon/auth/wecom_ldap_service.py,sha256=8mCOClbdlvky58ISXmV4UfSfUF0aWmPWykACNgjgfTg,30488
|
|
151
151
|
sycommon/config/Config.py,sha256=J5VjolqHmhYTBZwTyfSHv9X5cHMfN6kqY2ryHF4LJPw,6785
|
|
152
152
|
sycommon/config/DatabaseConfig.py,sha256=ILiUuYT9_xJZE2W-RYuC3JCt_YLKc1sbH13-MHIOPhg,804
|
|
153
153
|
sycommon/config/ElasticsearchConfig.py,sha256=fO9ZPMgJxSg1-UyDJ90wO6UvYy-jscwPJsSkXgx9qTU,2308
|
|
@@ -282,8 +282,8 @@ sycommon/tools/syemail.py,sha256=BDFhgf7WDOQeTcjxJEQdu0dQhnHFPO_p3eI0-Ni3LhQ,561
|
|
|
282
282
|
sycommon/tools/timing.py,sha256=OiiE7P07lRoMzX9kzb8sZU9cDb0zNnqIlY5pWqHcnkY,2064
|
|
283
283
|
sycommon/xxljob/__init__.py,sha256=7eoBlQxv-B39IfRSCY2bkqdGYs1QRe1umAWd88VMEEM,86
|
|
284
284
|
sycommon/xxljob/xxljob_service.py,sha256=JIEJaGXhqrTLcyxlyynSrsHg9bBnDNzX-D4qIWLRPUE,6815
|
|
285
|
-
sycommon_python_lib-0.2.
|
|
286
|
-
sycommon_python_lib-0.2.
|
|
287
|
-
sycommon_python_lib-0.2.
|
|
288
|
-
sycommon_python_lib-0.2.
|
|
289
|
-
sycommon_python_lib-0.2.
|
|
285
|
+
sycommon_python_lib-0.2.6a2.dist-info/METADATA,sha256=Utz4dW5hwd9N97fayxjevOMQ-wweDQZ2H_YdlsJmKl4,7913
|
|
286
|
+
sycommon_python_lib-0.2.6a2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
287
|
+
sycommon_python_lib-0.2.6a2.dist-info/entry_points.txt,sha256=gsR4SssKxDWjRU8ggidzNcdMXDPRSKRS7UaGyNP84Qg,92
|
|
288
|
+
sycommon_python_lib-0.2.6a2.dist-info/top_level.txt,sha256=RgphKrg7nJyZ7irJqbxFr-5H2LUYTvI7ivoWZH2hcD0,29
|
|
289
|
+
sycommon_python_lib-0.2.6a2.dist-info/RECORD,,
|
|
File without changes
|
{sycommon_python_lib-0.2.6a0.dist-info → sycommon_python_lib-0.2.6a2.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{sycommon_python_lib-0.2.6a0.dist-info → sycommon_python_lib-0.2.6a2.dist-info}/top_level.txt
RENAMED
|
File without changes
|