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.
@@ -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
- 本地开发(IS_DEV=true)时,使用 WeComDecodeUserID 配置的凭据,
274
- 线上直接使用 WeCom 配置的凭据。
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 或 llm.WeCom")
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
- IS_DEV=true 时使用 WeComDecodeUserID token,
378
- 线上使用 WeCom token。
373
+ 始终使用 WeCom 通讯录 token 查询用户详情。
374
+ 注意:WeComDecodeUserID token 只能用于 open_userid 解码,
375
+ 不能查 /cgi-bin/user/get(会返回 errcode 60111)。
379
376
  """
380
- token = await self._get_bot_access_token()
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
- # 保留原始传入的 wecom_userid
607
- result.wecom_userid = 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}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sycommon-python-lib
3
- Version: 0.2.6a0
3
+ Version: 0.2.6a2
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -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=VTGw8UQFBCH-lhJZ3N5TRcLy7Qk3Q4uENisXYFhkESk,24817
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.6a0.dist-info/METADATA,sha256=SZCYl4eAehrnZBS5MnF5Rr-VMP3XbDcKv5XbHQ8GPhI,7913
286
- sycommon_python_lib-0.2.6a0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
287
- sycommon_python_lib-0.2.6a0.dist-info/entry_points.txt,sha256=gsR4SssKxDWjRU8ggidzNcdMXDPRSKRS7UaGyNP84Qg,92
288
- sycommon_python_lib-0.2.6a0.dist-info/top_level.txt,sha256=RgphKrg7nJyZ7irJqbxFr-5H2LUYTvI7ivoWZH2hcD0,29
289
- sycommon_python_lib-0.2.6a0.dist-info/RECORD,,
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,,