's'
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
"""
|
||||
公众号助手(com.tencent.mp)「收到XXX的推荐」通知入库。
|
||||
|
||||
仅需 PocketBase 一个集合:mp_recommend_events(每条通知一行)。
|
||||
仅需 PocketBase 一个集合:mp_recommend_events(每个 mp_uid + recommender_key 一行,累计计数)。
|
||||
|
||||
- username Text 用户名(展示名,如从标题解析的 Eric)
|
||||
- cumulative_count Number 该用户截至本条通知的累计触发次数(每次 webhook 写入时 = 旧总数 + 1)
|
||||
- recommender_key Text 归一化键(casefold),与 username 对应,便于筛选,建议建索引
|
||||
- cumulative_count Number 该用户的累计触发次数(同一 mp_uid + recommender_key 每次 webhook 写入时 = 旧值 + 1)
|
||||
- recommender_key Text 归一化键(unicode 归一化 + 清洗 + casefold),与 username 对应,便于筛选,建议建索引
|
||||
- mp_uid Text 用户ID(用于把同名推荐人按不同账号隔离统计)
|
||||
|
||||
字段(在 PocketBase 中新建,名称一致):
|
||||
- recommender_key Text
|
||||
@@ -26,6 +27,7 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import re
|
||||
import unicodedata
|
||||
from typing import Any
|
||||
from urllib.parse import parse_qs
|
||||
|
||||
@@ -44,6 +46,26 @@ def _pb_escape(s: str) -> str:
|
||||
return (s or "").replace("\\", "\\\\").replace('"', '\\"')
|
||||
|
||||
|
||||
def _normalize_username(raw: str) -> str:
|
||||
"""
|
||||
尽量稳定地把展示名归一化到可用于统计/过滤的形式。
|
||||
- 兼容 emoji/颜文字/特殊字母:保留字符本身,不做 ASCII 限制
|
||||
- 统一空白:压缩为单个空格
|
||||
- Unicode 归一化:NFKC(把全角等兼容形态归一)
|
||||
- 大小写:仅在 key 上 casefold
|
||||
"""
|
||||
s = (raw or "")
|
||||
s = unicodedata.normalize("NFKC", s)
|
||||
s = s.replace("\u00A0", " ")
|
||||
s = re.sub(r"\s+", " ", s).strip()
|
||||
return s
|
||||
|
||||
|
||||
def _username_to_key(username: str) -> str:
|
||||
u = _normalize_username(username)
|
||||
return u.casefold() if u else "unknown"
|
||||
|
||||
|
||||
def parse_form_urlencoded_like(body: str) -> dict[str, str]:
|
||||
q = parse_qs(body, keep_blank_values=True, strict_parsing=False)
|
||||
return {
|
||||
@@ -103,19 +125,35 @@ def persist_mp_recommend(
|
||||
timestamp_raw: str,
|
||||
client_ip: str,
|
||||
) -> dict[str, Any]:
|
||||
"""写入一条事件;username、cumulative_count 写入本行(累计次数 = 同 key 已有条数 + 1)。"""
|
||||
"""按 mp_uid + recommender_key 聚合更新累计次数(存在则 +1,不存在则创建)。"""
|
||||
parsed = parse_mp_recommend_content(content)
|
||||
forward_ts = _parse_forward_ts_ms(timestamp_raw)
|
||||
|
||||
username = (parsed["recommender_name"] or "").strip() or "unknown"
|
||||
key = username.casefold() if username != "unknown" else "unknown"
|
||||
username = _normalize_username(parsed.get("recommender_name") or "") or "unknown"
|
||||
key = _username_to_key(username if username != "unknown" else "")
|
||||
mp_uid = (parsed.get("mp_uid") or "").strip()
|
||||
|
||||
pb = get_pb_client()
|
||||
events = pb.collection(COL_EVENTS)
|
||||
filt = f'recommender_key = "{_pb_escape(key)}"'
|
||||
lst = events.get_list(1, 1, {"filter": filt})
|
||||
total_before = int(getattr(lst, "total_items", 0) or 0)
|
||||
cumulative_count = total_before + 1
|
||||
|
||||
filt_parts = [f'recommender_key = "{_pb_escape(key)}"']
|
||||
if mp_uid:
|
||||
filt_parts.append(f'mp_uid = "{_pb_escape(mp_uid)}"')
|
||||
filt = " && ".join(filt_parts)
|
||||
|
||||
lst = events.get_list(1, 1, {"filter": filt, "sort": "-updated"})
|
||||
items = list(getattr(lst, "items", []) or [])
|
||||
existing = items[0] if items else None
|
||||
|
||||
old_count = 0
|
||||
existing_id = None
|
||||
if existing is not None:
|
||||
existing_id = getattr(existing, "id", None)
|
||||
try:
|
||||
old_count = int(getattr(existing, "cumulative_count", 0) or 0)
|
||||
except (TypeError, ValueError):
|
||||
old_count = 0
|
||||
cumulative_count = old_count + 1
|
||||
|
||||
event_payload: dict[str, Any] = {
|
||||
"recommender_key": key,
|
||||
@@ -124,7 +162,7 @@ def persist_mp_recommend(
|
||||
"package_name": parsed["package_name"],
|
||||
"title_line": parsed["title_line"],
|
||||
"summary_line": parsed["summary_line"],
|
||||
"mp_uid": parsed["mp_uid"],
|
||||
"mp_uid": mp_uid,
|
||||
"device_time_str": parsed["device_time_str"],
|
||||
"device_model": parsed["device_model"],
|
||||
"raw_content": parsed["raw_content"],
|
||||
@@ -134,6 +172,9 @@ def persist_mp_recommend(
|
||||
if forward_ts is not None:
|
||||
event_payload["forward_timestamp_ms"] = forward_ts
|
||||
|
||||
if existing_id:
|
||||
rec = events.update(existing_id, event_payload)
|
||||
else:
|
||||
rec = events.create(event_payload)
|
||||
event_id = getattr(rec, "id", None) or str(rec)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user