diff --git a/backend/app.py b/backend/app.py index b09f9c3..6171f58 100644 --- a/backend/app.py +++ b/backend/app.py @@ -81,7 +81,100 @@ with app.app_context(): db.session.commit() def push_feishu(date_str: str, amount: float, reason: str): - return + cfg = load_config() + url = cfg.get("feishu_webhook_url") + if not url: + return + shop = cfg.get("shop_name", "益选便利店") + try: + d = datetime.strptime(date_str, '%Y-%m-%d').date() + except Exception: + d = datetime.now().date() + y = d - timedelta(days=1) + r = DailyRevenue.query.filter_by(date=y).first() + y_amt = (r.amount if (r and r.is_final) else None) + arrow = '' + diff_str = '' + pct_str = '' + if isinstance(y_amt, (int, float)): + diff = amount - y_amt + arrow = '🔺' if diff >= 0 else '🔻' + diff_str = f"{'+' if diff >= 0 else '-'}{abs(diff):.2f}" + if y_amt != 0: + pct = abs(diff / y_amt) * 100.0 + pct_str = f"({'+' if diff >= 0 else '-'}{pct:.2f}%)" + today_line = f"**今日**:¥{amount:.2f}" + if isinstance(y_amt, (int, float)): + today_line += f" {arrow} {diff_str} {pct_str}".strip() + y_line = f"**昨日**:{('暂无数据' if y_amt is None else '¥' + format(y_amt, '.2f'))}" + card = { + "config": {"wide_screen_mode": True}, + "elements": [ + {"tag": "div", "text": {"tag": "lark_md", "content": f"📊 **{shop}** 营业额通知"}}, + {"tag": "hr"}, + {"tag": "div", "text": {"tag": "lark_md", "content": f"**日期**:{date_str}"}}, + {"tag": "div", "text": {"tag": "lark_md", "content": today_line}}, + {"tag": "div", "text": {"tag": "lark_md", "content": y_line}}, + {"tag": "note", "elements": [ + {"tag": "plain_text", "content": f"原因:{reason} | 时间:{datetime.now().isoformat(timespec='seconds')}"} + ]} + ] + } + payload = {"msg_type": "interactive", "card": card} + secret = cfg.get("feishu_secret") + if secret: + ts = str(int(time.time())) + sign_src = ts + "\n" + secret + sign = base64.b64encode(hmac.new(secret.encode(), sign_src.encode(), digestmod=hashlib.sha256).digest()).decode() + payload.update({"timestamp": ts, "sign": sign}) + def _log(s: str): + p = os.path.join(os.path.dirname(__file__), "..", "app.log") + with open(p, 'a', encoding='utf-8') as f: + f.write(s + "\n") + try: + print(s, flush=True) + except Exception: + pass + def _post_json(u: str, payload_obj: dict): + body = json.dumps(payload_obj, ensure_ascii=False).encode('utf-8') + return requests.post(u, data=body, headers={'Content-Type': 'application/json; charset=utf-8'}, timeout=5) + try: + is_feishu = ('open.feishu.cn' in url) + ok = False + if is_feishu: + resp = _post_json(url, payload) + ok = (200 <= resp.status_code < 300) + _log(f"飞书推送卡片{'成功' if ok else '失败'}: status={resp.status_code} body={resp.text[:500]}") + if not ok: + post_payload = { + "msg_type": "post", + "content": { + "post": { + "zh_cn": { + "title": f"{shop} 营业额通知", + "content": [[ + {"tag":"text","text": f"日期:{date_str}\n"}, + {"tag":"text","text": f"今日:¥{amount:.2f}"}, + *( [{"tag":"text","text": f" {arrow} {diff_str} {pct_str}"}] if isinstance(y_amt,(int,float)) else [] ), + ],[ + {"tag":"text","text": f"原因:{reason}"} + ]] + } + } + } + } + resp_post = _post_json(url, post_payload) + ok = (200 <= resp_post.status_code < 300) + _log(f"飞书推送POST{'成功' if ok else '失败'}: status={resp_post.status_code} body={resp_post.text[:500]}") + if not ok: + text = f"{shop}\n日期:{date_str}\n今日:¥{amount:.2f}" + if isinstance(y_amt, (int, float)): + text += f" {arrow} {diff_str} {pct_str}".strip() + text += f"\n原因:{reason}" + resp2 = _post_json(url, {"msg_type":"text","content":{"text": text}}) + _log(f"飞书推送文本{'成功' if (200 <= resp2.status_code < 300) else '失败'}: status={resp2.status_code} body={resp2.text[:500]}") + except Exception as e: + _log(f"飞书推送异常: {str(e)[:200]}") def generate_mock_revenue(): """保持原有逻辑:生成当日模拟营业额""" @@ -579,98 +672,3 @@ def sse_events(): yield "data: {\"type\": \"force_refresh\"}\n\n" time.sleep(30) return Response(stream_with_context(event_stream()), mimetype='text/event-stream') -def push_feishu(date_str: str, amount: float, reason: str): - cfg = load_config() - url = cfg.get("feishu_webhook_url") - if not url: - return - shop = cfg.get("shop_name", "益选便利店") - try: - d = datetime.strptime(date_str, '%Y-%m-%d').date() - except Exception: - d = datetime.now().date() - y = d - timedelta(days=1) - r = DailyRevenue.query.filter_by(date=y).first() - y_amt = (r.amount if (r and r.is_final) else None) - arrow = '' - diff_str = '' - pct_str = '' - if isinstance(y_amt, (int, float)): - diff = amount - y_amt - arrow = '🔺' if diff >= 0 else '🔻' - diff_str = f"{'+' if diff >= 0 else '-'}{abs(diff):.2f}" - if y_amt != 0: - pct = abs(diff / y_amt) * 100.0 - pct_str = f"({'+' if diff >= 0 else '-'}{pct:.2f}%)" - today_line = f"**今日**:¥{amount:.2f}" - if isinstance(y_amt, (int, float)): - today_line += f" {arrow} {diff_str} {pct_str}".strip() - y_line = f"**昨日**:{('暂无数据' if y_amt is None else '¥' + format(y_amt, '.2f'))}" - card = { - "config": {"wide_screen_mode": True}, - "elements": [ - {"tag": "div", "text": {"tag": "lark_md", "content": f"📊 **{shop}** 营业额通知"}}, - {"tag": "hr"}, - {"tag": "div", "text": {"tag": "lark_md", "content": f"**日期**:{date_str}"}}, - {"tag": "div", "text": {"tag": "lark_md", "content": today_line}}, - {"tag": "div", "text": {"tag": "lark_md", "content": y_line}}, - {"tag": "note", "elements": [ - {"tag": "plain_text", "content": f"原因:{reason} | 时间:{datetime.now().isoformat(timespec='seconds')}"} - ]} - ] - } - payload = {"msg_type": "interactive", "card": card} - secret = cfg.get("feishu_secret") - if secret: - ts = str(int(time.time())) - sign_src = ts + "\n" + secret - sign = base64.b64encode(hmac.new(secret.encode(), sign_src.encode(), digestmod=hashlib.sha256).digest()).decode() - payload.update({"timestamp": ts, "sign": sign}) - def _log(s: str): - p = os.path.join(os.path.dirname(__file__), "..", "app.log") - with open(p, 'a', encoding='utf-8') as f: - f.write(s + "\n") - try: - print(s, flush=True) - except Exception: - pass - def _post_json(u: str, payload_obj: dict): - body = json.dumps(payload_obj, ensure_ascii=False).encode('utf-8') - return requests.post(u, data=body, headers={'Content-Type': 'application/json; charset=utf-8'}, timeout=5) - try: - is_feishu = ('open.feishu.cn' in url) - ok = False - if is_feishu: - resp = _post_json(url, payload) - ok = (200 <= resp.status_code < 300) - _log(f"飞书推送卡片{'成功' if ok else '失败'}: status={resp.status_code} body={resp.text[:500]}") - if not ok: - post_payload = { - "msg_type": "post", - "content": { - "post": { - "zh_cn": { - "title": f"{shop} 营业额通知", - "content": [[ - {"tag":"text","text": f"日期:{date_str}\n"}, - {"tag":"text","text": f"今日:¥{amount:.2f}"}, - *( [{"tag":"text","text": f" {arrow} {diff_str} {pct_str}"}] if isinstance(y_amt,(int,float)) else [] ), - ],[ - {"tag":"text","text": f"原因:{reason}"} - ]] - } - } - } - } - resp_post = _post_json(url, post_payload) - ok = (200 <= resp_post.status_code < 300) - _log(f"飞书推送POST{'成功' if ok else '失败'}: status={resp_post.status_code} body={resp_post.text[:500]}") - if not ok: - text = f"{shop}\n日期:{date_str}\n今日:¥{amount:.2f}" - if isinstance(y_amt, (int, float)): - text += f" {arrow} {diff_str} {pct_str}".strip() - text += f"\n原因:{reason}" - resp2 = _post_json(url, {"msg_type":"text","content":{"text": text}}) - _log(f"飞书推送文本{'成功' if (200 <= resp2.status_code < 300) else '失败'}: status={resp2.status_code} body={resp2.text[:500]}") - except Exception as e: - _log(f"飞书推送异常: {str(e)[:200]}")