From d6765f51f26054408c4a78c323c5503f9fd63b8e Mon Sep 17 00:00:00 2001
From: 16337 <1633794139@qq.com>
Date: Fri, 3 Apr 2026 14:12:25 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=9A=E6=97=A5=E6=8A=A5?=
=?UTF-8?q?=E6=94=B9=E4=B8=BA=E5=8D=95=E6=9D=A1=E6=96=87=E6=9C=AC=E5=8D=A1?=
=?UTF-8?q?=E7=89=87=E6=8E=A8=E9=80=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/services/daily_report_service.py | 51 ++++++++++++++--------------
app/services/wechat_service.py | 29 ++++++++++++++++
2 files changed, 55 insertions(+), 25 deletions(-)
diff --git a/app/services/daily_report_service.py b/app/services/daily_report_service.py
index d9938b9..edf427c 100644
--- a/app/services/daily_report_service.py
+++ b/app/services/daily_report_service.py
@@ -28,7 +28,6 @@ CLEANING_TYPE_NAMES = {
}
WEEKDAY_NAMES = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
-REPORT_COVER_IMAGE_URL = "https://wework.qpic.cn/wwpic/252813_jOfDHtcISzuodLa_1629280209/0"
def _format_duration(minutes: float) -> str:
@@ -317,19 +316,27 @@ def _build_preview_text(report: Dict) -> str:
return "\n".join(lines)
-def _build_report_news(report: Dict) -> Dict:
+def _build_report_textcard(report: Dict) -> Dict:
summary = report["summary"]
click_url = settings.wechat.service_base_url or "https://work.weixin.qq.com"
- description = (
- f"{report['overview']}\n"
- f"首响 {summary['avg_resp']}|完结 {summary['avg_close']}|误报率 {summary['false_alarm_rate']}\n"
- f"高发区域:{report['tops']['areas']}"
- )
+ description_lines = [
+ f"
{report['subtitle']}
",
+ f"昨日新增 {summary['yesterday_total']} 条({report['change_str']})
",
+ f"昨日完成 {summary['completed_count']} 条|当前待处理 {summary['backlog_count']} 条
",
+ f"安保 {summary['security_count']} 条|保洁 {summary['clean_count']} 条
",
+ f"平均首响 {summary['avg_resp']}|平均完结 {summary['avg_close']}
",
+ f"误报率 {summary['false_alarm_rate']}|遗留待处理 {summary['carry_over_count']} 条
",
+ "重点风险
",
+ f"安保高发:{report['tops']['alarm_types']}
",
+ f"高发区域:{report['tops']['areas']}
",
+ f"高发摄像头:{report['tops']['cameras']}
",
+ ]
+ if report["top_overdue"]:
+ description_lines.append(f"优先跟进:{report['top_overdue'][0]}
")
return {
"title": report["title"],
- "description": description,
+ "description": "".join(description_lines),
"url": click_url,
- "picurl": REPORT_COVER_IMAGE_URL,
}
@@ -340,11 +347,11 @@ async def generate_daily_report() -> Optional[str]:
return _build_preview_text(report)
-async def generate_daily_report_news() -> Optional[Dict]:
+async def generate_daily_report_textcard() -> Optional[Dict]:
report = await _build_daily_report_data()
if not report:
return None
- return _build_report_news(report)
+ return _build_report_textcard(report)
async def _send_daily_report():
@@ -356,31 +363,25 @@ async def _send_daily_report():
return
try:
- news = await generate_daily_report_news()
+ card = await generate_daily_report_textcard()
preview = await generate_daily_report()
- if not news or not preview:
+ if not card or not preview:
logger.info("日报生成内容为空,跳过发送")
return
wechat_svc = get_wechat_service()
- ok = await wechat_svc.send_group_news(
+ ok = await wechat_svc.send_group_textcard(
chat_id=chat_id,
- title=news["title"],
- description=news["description"],
- url=news["url"],
- picurl=news["picurl"],
+ title=card["title"],
+ description=card["description"],
+ url=card["url"],
)
if ok:
- await asyncio.sleep(1)
- detail_ok = await wechat_svc.send_group_markdown(chat_id, preview)
- if detail_ok:
- logger.info("日报图文+摘要已发送到企微群聊")
- else:
- logger.warning("日报图文已发送,但摘要markdown发送失败")
+ logger.info("日报文本卡片已发送到企微群聊")
else:
fallback_ok = await wechat_svc.send_group_markdown(chat_id, preview)
if fallback_ok:
- logger.info("日报图文发送失败,已降级为markdown发送")
+ logger.info("日报文本卡片发送失败,已降级为markdown发送")
else:
logger.error("日报发送失败")
except Exception:
diff --git a/app/services/wechat_service.py b/app/services/wechat_service.py
index 034cdf4..b16b92b 100644
--- a/app/services/wechat_service.py
+++ b/app/services/wechat_service.py
@@ -588,6 +588,35 @@ class WeChatService:
logger.error(f"发送群聊markdown异常: {e}")
return False
+ async def send_group_textcard(self, chat_id: str, title: str, description: str, url: str = "") -> bool:
+ """发送 textcard 消息到群聊,适合单条摘要展示。"""
+ if not self._enabled:
+ return False
+ try:
+ access_token = await self._get_access_token()
+ msg = {
+ "chatid": chat_id,
+ "msgtype": "textcard",
+ "textcard": {
+ "title": title,
+ "description": description,
+ "url": url or "https://work.weixin.qq.com",
+ "btntxt": "查看详情",
+ },
+ }
+ api_url = f"https://qyapi.weixin.qq.com/cgi-bin/appchat/send?access_token={access_token}"
+ async with httpx.AsyncClient(timeout=10) as client:
+ resp = await client.post(api_url, json=msg)
+ data = resp.json()
+ if data.get("errcode") != 0:
+ logger.error(f"群聊文本卡片发送失败: {data}")
+ return False
+ logger.info(f"群聊文本卡片已发送: chatid={chat_id}")
+ return True
+ except Exception as e:
+ logger.error(f"发送群聊文本卡片异常: {e}")
+ return False
+
async def send_group_image(self, chat_id: str, media_id: str) -> bool:
"""发送图片消息到群聊"""
if not self._enabled: