修改企业微信推送
This commit is contained in:
+6
-3
@@ -16,7 +16,7 @@ from .periods import get_daybefore_period, get_today_period, get_yesterday_perio
|
||||
from .scheduler import job_finalize_yesterday, job_push_wecom, job_refresh_today, start_scheduler, startup_tasks, stop_scheduler
|
||||
from .stats import compute_three_day_stats
|
||||
from .aggregator import aggregate_period
|
||||
from .wecom import build_markdown, build_push_payload, send_wecom_markdown
|
||||
from .wecom import build_markdown, build_push_payload, send_push_payload
|
||||
from .state import get_today_cache
|
||||
|
||||
logging.basicConfig(
|
||||
@@ -111,14 +111,17 @@ async def api_push_test():
|
||||
snap = get_latest_snapshot("yesterday")
|
||||
if not snap:
|
||||
raise HTTPException(500, "无法生成昨日数据")
|
||||
ok, msg = await send_wecom_markdown(payload["markdown"])
|
||||
ok, msg = await send_push_payload(payload)
|
||||
log_push(snap["period_start"], snap["period_end"], ok, msg)
|
||||
if not ok:
|
||||
raise HTTPException(500, f"推送失败: {msg}")
|
||||
parts = payload.get("parts", 1)
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"已推送 {payload.get('count', 0)} 个三日交集币种",
|
||||
"message": f"已推送 {payload.get('count', 0)} 个币种"
|
||||
+ (f"(分 {parts} 条消息)" if parts > 1 else ""),
|
||||
"count": payload.get("count", 0),
|
||||
"parts": parts,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ from .periods import get_daybefore_period, get_today_period, get_yesterday_perio
|
||||
from .state import get_today_cache, set_today_cache
|
||||
from .funding_store import prefetch_funding
|
||||
from .kline_store import prefetch_symbols
|
||||
from .wecom import build_push_payload, send_wecom_markdown
|
||||
from .wecom import build_push_payload, send_push_payload
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -112,7 +112,7 @@ async def job_push_wecom() -> None:
|
||||
if not payload.get("ok"):
|
||||
logger.warning("WeCom push skipped: %s", payload.get("message"))
|
||||
return
|
||||
ok, msg = await send_wecom_markdown(payload["markdown"])
|
||||
ok, msg = await send_push_payload(payload)
|
||||
log_push(ps, pe, ok, msg)
|
||||
if ok:
|
||||
logger.info("WeCom push succeeded")
|
||||
|
||||
+88
-35
@@ -9,6 +9,9 @@ from .stats import compute_three_day_stats
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 企业微信 markdown 单条上限 4096,预留余量
|
||||
WECOM_MD_MAX = 3800
|
||||
|
||||
|
||||
def _format_period_label(period_start: str, period_end: str) -> str:
|
||||
start = period_start[:16].replace("T", " ")
|
||||
@@ -16,17 +19,64 @@ def _format_period_label(period_start: str, period_end: str) -> str:
|
||||
return f"{start} ~ {end}"
|
||||
|
||||
|
||||
def _day_line(label: str, row: dict | None) -> str:
|
||||
def _day_seg(label: str, row: dict | None) -> str:
|
||||
if not row or row.get("rank") is None:
|
||||
return f"> {label}:—"
|
||||
pct = row.get("price_change_pct_fmt") or f"{row.get('price_change_pct', 0):+.2f}%"
|
||||
vol = row.get("quote_volume_fmt") or str(row.get("quote_volume", ""))
|
||||
fr = row.get("funding_rate_fmt") or "—"
|
||||
return f"> {label}:#{row['rank']} · 额 {vol} · 涨跌 {pct} · 费率 {fr}"
|
||||
return f"{label}—"
|
||||
pct = row.get("price_change_pct_fmt") or f"{row.get('price_change_pct', 0):+.1f}%"
|
||||
return f"{label}#{row['rank']}{pct}"
|
||||
|
||||
|
||||
def _coin_line(rank: int, row: dict) -> str:
|
||||
sym = row["symbol"]
|
||||
y, t, b = row.get("yesterday"), row.get("today"), row.get("daybefore")
|
||||
return (
|
||||
f"**{rank}. {sym}** "
|
||||
f"{_day_seg('昨', y)} {_day_seg('今', t)} {_day_seg('前', b)}"
|
||||
)
|
||||
|
||||
|
||||
def _build_header(period_label: str, count: int, part: int | None = None) -> list[str]:
|
||||
title = "## 币安 U本位 · 三日Top30交集"
|
||||
if part and part > 1:
|
||||
title += f"({part})"
|
||||
lines = [
|
||||
title,
|
||||
"",
|
||||
f"> 昨日周期 {period_label}",
|
||||
f"> 共 **{count}** 个 · 昨/今/前 = 排名+涨跌幅",
|
||||
"",
|
||||
]
|
||||
return lines
|
||||
|
||||
|
||||
def _split_messages(
|
||||
period_label: str, count: int, items: list[dict]
|
||||
) -> list[str]:
|
||||
"""按企微长度限制拆成多条 markdown。"""
|
||||
if not items:
|
||||
body = "\n".join(_build_header(period_label, 0) + ["**暂无交集币种**"])
|
||||
return [body]
|
||||
|
||||
messages: list[str] = []
|
||||
part = 1
|
||||
lines = _build_header(period_label, count, part)
|
||||
for i, row in enumerate(items, 1):
|
||||
coin = _coin_line(i, row)
|
||||
extra = len(coin) + 1
|
||||
if len("\n".join(lines)) + extra > WECOM_MD_MAX and len(lines) > 5:
|
||||
messages.append("\n".join(lines).rstrip())
|
||||
part += 1
|
||||
lines = _build_header(period_label, count, part)
|
||||
lines.append(coin)
|
||||
if part == 1:
|
||||
lines.append("")
|
||||
lines.append("> 仅三日均为 Top30 交集,涨跌不限")
|
||||
messages.append("\n".join(lines).rstrip())
|
||||
return messages
|
||||
|
||||
|
||||
def build_push_payload() -> dict[str, Any]:
|
||||
"""构建企微推送内容:仅三日 Top30 交集,列表排版(非表格)。"""
|
||||
"""构建企微推送:仅三日 Top30 交集,紧凑单行/币,超长自动分批。"""
|
||||
stats = compute_three_day_stats()
|
||||
periods = stats.get("periods") or {}
|
||||
y_meta = periods.get("yesterday") or {}
|
||||
@@ -53,65 +103,51 @@ def build_push_payload() -> dict[str, Any]:
|
||||
"count": 0,
|
||||
"period_label": period_label,
|
||||
"markdown": md,
|
||||
"messages": [md],
|
||||
"parts": 1,
|
||||
"items": [],
|
||||
}
|
||||
|
||||
items = stats.get("items") or []
|
||||
lines = [
|
||||
"## 币安 U本位 · 三日Top30交集",
|
||||
"",
|
||||
f"> **昨日周期**(北京时间 8:00 切日)",
|
||||
f"> {period_label}",
|
||||
f"> 连续三日成交额均为 Top{settings.top_n},共 **{len(items)}** 个",
|
||||
"",
|
||||
]
|
||||
messages = _split_messages(period_label, len(items), items)
|
||||
|
||||
preview_items: list[dict] = []
|
||||
for i, row in enumerate(items, 1):
|
||||
sym = row["symbol"]
|
||||
t, y, b = row.get("today"), row.get("yesterday"), row.get("daybefore")
|
||||
lines.append(f"### {i}. {sym}")
|
||||
lines.append(_day_line("昨日", y))
|
||||
lines.append(_day_line("今日", t))
|
||||
lines.append(_day_line("前日", b))
|
||||
lines.append("")
|
||||
preview_items.append(
|
||||
{
|
||||
"rank": i,
|
||||
"symbol": sym,
|
||||
"today": t,
|
||||
"yesterday": y,
|
||||
"daybefore": b,
|
||||
"symbol": row["symbol"],
|
||||
"today": row.get("today"),
|
||||
"yesterday": row.get("yesterday"),
|
||||
"daybefore": row.get("daybefore"),
|
||||
"total_quote_volume": row.get("total_quote_volume"),
|
||||
}
|
||||
)
|
||||
|
||||
if not items:
|
||||
lines.append("**暂无交集币种**(请确认今日/昨日/前日快照均已生成)")
|
||||
|
||||
lines.append("---")
|
||||
lines.append("> 说明:仅推送三日均为成交额 Top30 的合约;涨跌不限")
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"message": stats.get("criteria", ""),
|
||||
"count": len(items),
|
||||
"period_label": period_label,
|
||||
"markdown": "\n".join(lines),
|
||||
"markdown": messages[0] if messages else "",
|
||||
"messages": messages,
|
||||
"parts": len(messages),
|
||||
"items": preview_items,
|
||||
}
|
||||
|
||||
|
||||
def build_markdown(snapshot: dict | None = None) -> str:
|
||||
"""兼容旧调用:返回企微 Markdown 文本(忽略 snapshot,以三日交集为准)。"""
|
||||
_ = snapshot
|
||||
return build_push_payload()["markdown"]
|
||||
payload = build_push_payload()
|
||||
return payload.get("markdown") or ""
|
||||
|
||||
|
||||
async def send_wecom_markdown(content: str) -> tuple[bool, str]:
|
||||
url = settings.wecom_webhook_url.strip()
|
||||
if not url:
|
||||
return False, "WECOM_WEBHOOK_URL 未配置"
|
||||
if len(content) > 4096:
|
||||
return False, f"内容过长({len(content)}字),请使用分批推送"
|
||||
payload = {"msgtype": "markdown", "markdown": {"content": content}}
|
||||
last_err = ""
|
||||
for attempt in range(3):
|
||||
@@ -128,3 +164,20 @@ async def send_wecom_markdown(content: str) -> tuple[bool, str]:
|
||||
last_err = str(e)
|
||||
logger.warning("WeCom push attempt %d failed: %s", attempt + 1, e)
|
||||
return False, last_err
|
||||
|
||||
|
||||
async def send_push_payload(payload: dict) -> tuple[bool, str]:
|
||||
"""发送推送,超长时按 messages 列表逐条发送。"""
|
||||
parts = payload.get("messages") or [payload.get("markdown", "")]
|
||||
if not parts or not parts[0]:
|
||||
return False, "无推送内容"
|
||||
for i, content in enumerate(parts, 1):
|
||||
ok, msg = await send_wecom_markdown(content)
|
||||
if not ok:
|
||||
suffix = f"(第 {i}/{len(parts)} 条)" if len(parts) > 1 else ""
|
||||
return False, f"{msg}{suffix}"
|
||||
if i < len(parts):
|
||||
logger.info("WeCom push part %d/%d sent", i, len(parts))
|
||||
if len(parts) > 1:
|
||||
return True, f"已分 {len(parts)} 条发送"
|
||||
return True, "ok"
|
||||
|
||||
+3
-1
@@ -356,7 +356,9 @@ function renderWecomPreview(payload) {
|
||||
return;
|
||||
}
|
||||
if (meta) {
|
||||
meta.textContent = `${payload.period_label || "—"} · ${payload.count} 个币种`;
|
||||
const partsHint =
|
||||
payload.parts > 1 ? ` · 企微分 ${payload.parts} 条发送` : "";
|
||||
meta.textContent = `${payload.period_label || "—"} · ${payload.count} 个币种${partsHint}`;
|
||||
}
|
||||
if (!payload.items?.length) {
|
||||
cards.innerHTML = '<p class="loading">暂无三日交集币种</p>';
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@
|
||||
<p class="stats-desc" id="stats-desc"></p>
|
||||
<section class="wecom-preview-panel hidden" id="wecom-preview-panel">
|
||||
<h3>企微推送预览 <span class="wecom-preview-meta" id="wecom-preview-meta"></span></h3>
|
||||
<p class="stats-desc">仅包含「三日 Top30 交集」币种;实际发到企微为下方同款列表排版(非宽表格)。</p>
|
||||
<p class="stats-desc">仅包含「三日 Top30 交集」币种;企微为紧凑单行/币,超过 4096 字自动分多条发送。</p>
|
||||
<div id="wecom-preview-cards" class="wecom-preview-cards"></div>
|
||||
</section>
|
||||
<div class="table-wrap" id="stats-table-wrap"></div>
|
||||
|
||||
Reference in New Issue
Block a user