@@ -190,11 +190,11 @@ def _send_feishu_message(self, content: str, *, timeout_seconds: Optional[float]
190190 prepared_content = self ._apply_keyword_prefix (content )
191191 security_fields = self ._build_security_fields ()
192192
193- def _post_payload (payload : Dict [str , Any ]) -> bool :
193+ def _post_payload (payload : Dict [str , Any ], payload_text : str = prepared_content ) -> bool :
194194 request_payload = dict (payload )
195195 request_payload .update (security_fields )
196196 logger .debug (f"飞书请求 URL: { self ._feishu_url } " )
197- logger .debug (f"飞书请求 payload 长度: { len (prepared_content )} 字符" )
197+ logger .debug (f"飞书请求 payload 长度: { len (payload_text )} 字符" )
198198
199199 response = requests .post (
200200 self ._feishu_url ,
@@ -223,6 +223,63 @@ def _post_payload(payload: Dict[str, Any]) -> bool:
223223 logger .error (f"响应内容: { response .text } " )
224224 return False
225225
226+ def _post_text_payload (text_content : str ) -> bool :
227+ prepared_text = self ._apply_keyword_prefix (text_content )
228+ text_payload = {
229+ "msg_type" : "text" ,
230+ "content" : {
231+ "text" : prepared_text
232+ }
233+ }
234+ return _post_payload (text_payload , prepared_text )
235+
236+ def _send_fallback_text (text_content : str ) -> bool :
237+ max_bytes = self ._feishu_max_bytes
238+ keyword_overhead = len (self ._get_keyword_prefix ().encode ('utf-8' ))
239+ effective_max_bytes = max_bytes - keyword_overhead
240+
241+ if effective_max_bytes <= 0 :
242+ logger .error ("飞书关键词过长,超过单条消息允许的最大字节数,无法发送" )
243+ return False
244+
245+ text_bytes = len (text_content .encode ('utf-8' )) + keyword_overhead
246+ if text_bytes <= max_bytes :
247+ return _post_text_payload (text_content )
248+
249+ min_chunk_bytes = MIN_MAX_BYTES + PAGE_MARKER_SAFE_BYTES
250+ if effective_max_bytes < min_chunk_bytes :
251+ logger .error (
252+ "飞书回退文本分片预算(%s字节)不足以安全分页发送,至少需要 %s 字节" ,
253+ effective_max_bytes ,
254+ min_chunk_bytes ,
255+ )
256+ return False
257+
258+ try :
259+ chunks = chunk_content_by_max_bytes (
260+ text_content ,
261+ effective_max_bytes ,
262+ add_page_marker = True ,
263+ )
264+ except ValueError as e :
265+ logger .error ("飞书回退文本分片失败: %s" , e )
266+ return False
267+
268+ success_count = 0
269+ total_chunks = len (chunks )
270+ logger .info ("飞书回退文本超长(%s字节/%s字符),将分批发送:共 %s 批" , text_bytes , len (text_content ), total_chunks )
271+ for i , chunk in enumerate (chunks ):
272+ if _post_text_payload (chunk ):
273+ success_count += 1
274+ logger .info ("飞书回退文本第 %s/%s 批发送成功" , i + 1 , total_chunks )
275+ else :
276+ logger .error ("飞书回退文本第 %s/%s 批发送失败" , i + 1 , total_chunks )
277+
278+ if i < total_chunks - 1 :
279+ time .sleep (1 )
280+
281+ return success_count == total_chunks
282+
226283 # 1) 优先使用结构化交互卡片。飞书的 lark_md 不是完整 Markdown,
227284 # 表格/代码块/引用需要拆成卡片元素才会真正渲染。
228285 card_elements = build_feishu_card_elements (prepared_content )
@@ -243,12 +300,6 @@ def _post_payload(payload: Dict[str, Any]) -> bool:
243300 if _post_payload (card_payload ):
244301 return True
245302
246- # 2) 回退为普通文本消息
247- text_payload = {
248- "msg_type" : "text" ,
249- "content" : {
250- "text" : format_feishu_markdown (prepared_content )
251- }
252- }
253-
254- return _post_payload (text_payload )
303+ # 2) 回退为普通文本消息。回退格式可能比原始 Markdown 更长,
304+ # 发送前按真实回退文本重新校验并必要时分片。
305+ return _send_fallback_text (format_feishu_markdown (content ))
0 commit comments