This commit is contained in:
eric
2026-04-02 05:26:11 -05:00
parent 1c5b46a933
commit a59592bed5

View File

@@ -1,11 +1,12 @@
""" """
公众号助手com.tencent.mp「收到XXX的推荐」通知入库。 公众号助手com.tencent.mp「收到XXX的推荐」通知入库。
仅需 PocketBase 一个集合mp_recommend_events条通知一行)。 仅需 PocketBase 一个集合mp_recommend_events个 mp_uid + recommender_key 一行,累计计数)。
- username Text 用户名(展示名,如从标题解析的 Eric - username Text 用户名(展示名,如从标题解析的 Eric
- cumulative_count Number 该用户截至本条通知的累计触发次数(每次 webhook 写入时 = 旧总数 + 1 - cumulative_count Number 该用户的累计触发次数(同一 mp_uid + recommender_key 每次 webhook 写入时 = 旧 + 1
- recommender_key Text 归一化键casefold与 username 对应,便于筛选,建议建索引 - recommender_key Text 归一化键(unicode 归一化 + 清洗 + casefold与 username 对应,便于筛选,建议建索引
- mp_uid Text 用户ID用于把同名推荐人按不同账号隔离统计
字段(在 PocketBase 中新建,名称一致): 字段(在 PocketBase 中新建,名称一致):
- recommender_key Text - recommender_key Text
@@ -26,6 +27,7 @@ from __future__ import annotations
import logging import logging
import re import re
import unicodedata
from typing import Any from typing import Any
from urllib.parse import parse_qs from urllib.parse import parse_qs
@@ -44,6 +46,26 @@ def _pb_escape(s: str) -> str:
return (s or "").replace("\\", "\\\\").replace('"', '\\"') 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]: def parse_form_urlencoded_like(body: str) -> dict[str, str]:
q = parse_qs(body, keep_blank_values=True, strict_parsing=False) q = parse_qs(body, keep_blank_values=True, strict_parsing=False)
return { return {
@@ -103,19 +125,35 @@ def persist_mp_recommend(
timestamp_raw: str, timestamp_raw: str,
client_ip: str, client_ip: str,
) -> dict[str, Any]: ) -> dict[str, Any]:
"""写入一条事件username、cumulative_count 写入本行(累计次数 = 同 key 已有条数 + 1)。""" """按 mp_uid + recommender_key 聚合更新累计次数(存在则 +1不存在则创建)。"""
parsed = parse_mp_recommend_content(content) parsed = parse_mp_recommend_content(content)
forward_ts = _parse_forward_ts_ms(timestamp_raw) forward_ts = _parse_forward_ts_ms(timestamp_raw)
username = (parsed["recommender_name"] or "").strip() or "unknown" username = _normalize_username(parsed.get("recommender_name") or "") or "unknown"
key = username.casefold() if username != "unknown" else "unknown" key = _username_to_key(username if username != "unknown" else "")
mp_uid = (parsed.get("mp_uid") or "").strip()
pb = get_pb_client() pb = get_pb_client()
events = pb.collection(COL_EVENTS) events = pb.collection(COL_EVENTS)
filt = f'recommender_key = "{_pb_escape(key)}"'
lst = events.get_list(1, 1, {"filter": filt}) filt_parts = [f'recommender_key = "{_pb_escape(key)}"']
total_before = int(getattr(lst, "total_items", 0) or 0) if mp_uid:
cumulative_count = total_before + 1 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] = { event_payload: dict[str, Any] = {
"recommender_key": key, "recommender_key": key,
@@ -124,7 +162,7 @@ def persist_mp_recommend(
"package_name": parsed["package_name"], "package_name": parsed["package_name"],
"title_line": parsed["title_line"], "title_line": parsed["title_line"],
"summary_line": parsed["summary_line"], "summary_line": parsed["summary_line"],
"mp_uid": parsed["mp_uid"], "mp_uid": mp_uid,
"device_time_str": parsed["device_time_str"], "device_time_str": parsed["device_time_str"],
"device_model": parsed["device_model"], "device_model": parsed["device_model"],
"raw_content": parsed["raw_content"], "raw_content": parsed["raw_content"],
@@ -134,7 +172,10 @@ def persist_mp_recommend(
if forward_ts is not None: if forward_ts is not None:
event_payload["forward_timestamp_ms"] = forward_ts event_payload["forward_timestamp_ms"] = forward_ts
rec = events.create(event_payload) 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) event_id = getattr(rec, "id", None) or str(rec)
logger.info( logger.info(