Skip to content

Commit 04738a4

Browse files
committed
fix(review-feedback-1265): address latest review comments
1 parent 553f2b2 commit 04738a4

2 files changed

Lines changed: 49 additions & 2 deletions

File tree

src/notification_sender/feishu_sender.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,20 @@
2727
logger = logging.getLogger(__name__)
2828

2929

30+
def _contains_markdown_table(content: str) -> bool:
31+
"""Return whether content contains consecutive Markdown table rows."""
32+
table_row_count = 0
33+
for raw_line in content.splitlines():
34+
stripped = raw_line.strip()
35+
if stripped.startswith("|") and "|" in stripped[1:]:
36+
table_row_count += 1
37+
if table_row_count >= 2:
38+
return True
39+
continue
40+
table_row_count = 0
41+
return False
42+
43+
3044
class FeishuSender:
3145

3246
def __init__(self, config: Config):
@@ -148,7 +162,8 @@ def _send_feishu_chunked(self, content: str, max_bytes: int) -> bool:
148162
"""
149163
分批发送长消息到飞书
150164
151-
按股票分析块(以 --- 或 ### 分隔)智能分割,确保每批不超过限制
165+
按股票分析块(以 --- 或 ### 分隔)智能分割,确保每批不超过限制。
166+
包含表格时先转成飞书友好的安全文本,避免分片后丢失表头上下文。
152167
153168
Args:
154169
content: 完整消息内容
@@ -157,8 +172,9 @@ def _send_feishu_chunked(self, content: str, max_bytes: int) -> bool:
157172
Returns:
158173
是否全部发送成功
159174
"""
175+
chunk_source = format_feishu_markdown(content) if _contains_markdown_table(content) else content
160176
try:
161-
chunks = chunk_content_by_max_bytes(content, max_bytes, add_page_marker=True)
177+
chunks = chunk_content_by_max_bytes(chunk_source, max_bytes, add_page_marker=True)
162178
except ValueError as e:
163179
logger.error("飞书消息分片失败,单片预算不足以安全分页(关键词过长或 max_bytes 过小): %s", e)
164180
return False

tests/test_notification_sender.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,37 @@ def test_send_markdown_table_uses_structured_card_elements(self, mock_post):
229229
self.assertNotIn("```", serialized)
230230
self.assertNotIn("| 指标 |", serialized)
231231

232+
@mock.patch("src.notification_sender.feishu_sender.time.sleep", return_value=None)
233+
@mock.patch("src.notification_sender.feishu_sender.requests.post")
234+
def test_chunked_markdown_table_does_not_promote_data_rows_to_headers(self, mock_post, _mock_sleep):
235+
mock_post.return_value = _response(200, {"code": 0})
236+
cfg = _config(feishu_webhook_url="https://feishu.example/hook", feishu_max_bytes=130)
237+
sender = FeishuSender(cfg)
238+
rows = "\n".join(
239+
f"| {index:06d} | 股票{index:02d} | {index} |"
240+
for index in range(1, 10)
241+
)
242+
content = f"""# 人气股票
243+
244+
| 代码 | 名称 | 热度 |
245+
| --- | --- | --- |
246+
{rows}
247+
"""
248+
249+
result = sender.send_to_feishu(content)
250+
251+
self.assertTrue(result)
252+
self.assertGreater(mock_post.call_count, 1)
253+
continuation_elements = [
254+
call.kwargs["json"]["card"]["elements"]
255+
for call in mock_post.call_args_list[1:]
256+
]
257+
serialized = json.dumps(continuation_elements, ensure_ascii=False)
258+
self.assertIn("000004", serialized)
259+
self.assertIn("000006", serialized)
260+
self.assertNotIn("**000002**", serialized)
261+
self.assertNotIn("**000006**", serialized)
262+
232263
@mock.patch("src.notification_sender.feishu_sender.requests.post")
233264
def test_send_error_response_returns_false(self, mock_post):
234265
mock_post.return_value = _response(200, {"code": 19024, "msg": "keyword not found"})

0 commit comments

Comments
 (0)