1. 模块演进目标与设计原则

传统“星座运势”类应用常陷入“算命陷阱”:用户打开App → 输入生日 → 获取一段静态文本 → 关闭。这种单次、无状态、低参与的交互,既无法沉淀用户价值,也难以支撑长期产品迭代。我们的演进目标很明确:不止于算命,而在于构建可生长的「灵性体验智能体」——以用户为中心,将星座、塔罗等符号系统转化为动态理解用户的语义接口。

为此,我们确立三大刚性设计原则:
可扩展性:新占卜体系(如北欧符文、印度Jyotish)应能在不重启服务、不修改核心逻辑的前提下,通过配置+插件方式接入;
隐私合规:默认遵循GDPR与《个人信息保护法》,所有PII字段强制加密,数据采集遵循“最小必要+显式授权”双基线;
低耦合:星座偏好、塔罗行为、解读风格等维度必须物理隔离,禁止跨模块直接读库或共享内存。

架构对比一目了然:

  • 基础版(v1.0):单体后端直连MySQL,/api/v1/horoscope?sign=libra&date=today 返回JSON文本,无用户上下文;
  • 增强版(v2.0):前端调用统一网关 /api/v2/reading?context=career → 网关路由至 insight-service → 并行调用 astro-service + tarot-service + user-profile-service → 融合生成个性化解读。

UML组件图:星座/塔罗模块与用户画像服务解耦结构

演进路线图清晰分阶段:

  • v1.0(已上线):支持单次占卜、基础埋点、本地用户存储;
  • v1.5(Q3交付):上线用户画像服务、Flink实时特征管道、OpenID Connect鉴权;
  • v2.0(Q4 GA):完成微服务拆分、混合推荐引擎上线、GDPR自动化擦除接口就绪。

⚠️ 注意事项:OpenID Connect 必须启用 prompt=consent 强制二次确认;所有敏感操作(如生日录入)需单独弹窗声明用途,并提供“跳过”选项;配置中心(如Nacos)统一管理星座标签、塔罗牌组映射表,严禁硬编码。


2. 用户画像数据模型设计(含代码示例)

用户画像不是“打标签”,而是构建可计算的行为语义空间。我们定义三个核心实体:

  • UserProfile:用户主干身份(不可变ID + 可变特征快照);
  • BehaviorLog:原子事件流(抽牌、停留、分享、跳过),带毫秒级时间戳与会话ID;
  • TraitVector:从行为聚合出的向量化特征(如“直觉型解读倾向得分”),供推荐引擎消费。

以下是生产级 Pydantic v2 模型(支持动态字段扩展与校验):

from pydantic import BaseModel, Field, validator
from typing import Literal, Optional, Dict, Any
from datetime import datetime

class UserTrait(BaseModel):
    astro_sign: Optional[Literal[
        "aries", "taurus", "gemini", "cancer", "leo", "virgo",
        "libra", "scorpio", "sagittarius", "capricorn", "aquarius", "pisces"
    ]] = None
    tarot_engagement_score: float = Field(ge=0, le=100, description="0-100加权分,基于点击/收藏/分享/停留时长")
    interpretive_style: Literal["intuitive", "analytical", "narrative"] = "intuitive"
    
    @validator('astro_sign', always=True)
    def default_unknown(cls, v):
        return v or "unknown"

class UserProfile(BaseModel):
    user_id: str = Field(..., min_length=12, max_length=32, regex=r'^[a-zA-Z0-9_]+$')
    traits: UserTrait
    updated_at: datetime
    # 敏感字段不在此处定义!生日、手机号等走独立加密存储通道

双数据库实现策略

  • MongoDB(行为日志):文档天然嵌套,BehaviorLog 直接存为 { user_id: "...", event: "tarot_interpret", payload: { card: "queen_of_cups", duration_ms: 8420 } }
  • PostgreSQL(画像主表):严格范式化,user_profile 表仅存 user_id, updated_at, traits_jsonb(JSONB类型,支持Gin索引查询)。

🔐 安全铁律:

  • 所有含生日字段(如原始输入)必须经 AES-256-GCM 加密后存入独立 pii_encrypted 表,密钥由KMS托管;
  • Q:用户未授权星座信息?A:astro_sign 字段允许 None,模型层默认设为 "unknown",后续通过聚类(如:高频点击水象牌+长停留→概率补全为 cancer)渐进优化。

3. 行为埋点与实时特征计算(手把手接入)

埋点不是“加个 analytics.js”,而是构建可信赖的数据源。我们采用前端轻量SDK + 后端流式处理闭环。

步骤1:前端埋点注入(React示例)

// 使用自研 SDK(<3KB gzip,CDN加载)
import { trackEvent } from '@spirit/sdk';

const TarotDrawer = () => {
  const handleCardDraw = () => {
    trackEvent("tarot_draw", {
      card_id: selectedCard.id,
      deck_type: "ryder-waite",
      session_id: getSessionId(), // UUIDv4,localStorage持久化,防刷新丢失
      timestamp: Date.now()
    });
  };
  return <button onClick={handleCardDraw}>抽取塔罗牌</button>;
};

✅ 埋点SDK关键保障:

  • 通过 <script async src="https://cdn.example.com/spirit-sdk-v1.2.min.js"> 加载,绝不阻塞渲染;
  • 网络失败时自动写入 localStorage,恢复后批量重传(附代码片段):
// localStorage 缓存 + 重传
const queue = JSON.parse(localStorage.getItem('event_queue') || '[]');
queue.push(event);
localStorage.setItem('event_queue', JSON.stringify(queue));

// 定时重传(页面可见时触发)
document.addEventListener('visibilitychange', () => {
  if (!document.hidden && queue.length > 0) {
    fetch('/api/v1/track', { method: 'POST', body: JSON.stringify(queue) })
      .then(() => { localStorage.removeItem('event_queue'); });
  }
});
-- 创建行为流表(Kafka source)
CREATE TABLE behavior_stream (
  user_id STRING,
  event_type STRING,
  session_id STRING,
  duration_ms BIGINT,
  proctime AS PROCTIME()
) WITH (
  'connector' = 'kafka',
  'topic' = 'user-behavior',
  'properties.bootstrap.servers' = 'kafka:9092'
);

-- 实时计算1小时窗口活跃度指标
INSERT INTO user_features 
SELECT 
  user_id,
  COUNT(*) AS draw_count_1h,
  AVG(duration_ms) AS avg_interpret_time_1h,
  UNIX_TIMESTAMP() AS ts
FROM behavior_stream 
WHERE event_type IN ('tarot_draw', 'tarot_interpret')
GROUP BY user_id, TUMBLING (proctime, INTERVAL '1' HOUR);

⚙️ Flink 生产配置:checkpointInterval=60s, state.backend=rocksdb, execution.checkpointing.mode=EXACTLY_ONCE


4. 星座/塔罗模块的可插拔式扩展(微服务改造)

单体架构下,新增一个“凯尔特十字阵解读”功能需改3个模块、测全链路、停服发布。微服务改造后,只需:
① 在 tarot-service 新增 circular-spread.py 解析器;
② 更新 insight-service 的策略配置;
③ 发布 tarot-service 单独实例。

服务职责划分:

  • astro-service:提供 /horoscope(今日运势)、/compatibility(配对分析);
  • tarot-service:提供 /draw(抽牌)、/interpret(牌阵解读);
  • insight-service:融合画像 + 上下文,调用上下游并编排响应。

gRPC 接口定义(insight_service.proto):

service InsightService {
  rpc GetPersonalizedReading(ReadingRequest) returns (ReadingResponse);
}
message ReadingRequest {
  string user_id = 1;
  string context = 2; // e.g., "career", "relationship"
  string preferred_language = 3; // i18n support
}

Python gRPC客户端调用(异步):

import asyncio
import aio
import astro_pb2, astro_pb2_grpc

async def get_reading(user_id: str) -> dict:
    async with aio.insecure_channel('astro-service:50051') as channel:
        stub = astro_pb2_grpc.AstroServiceStub(channel)
        resp = await stub.GetHoroscope(
            astro_pb2.HoroscopeRequest(
                sign=get_user_sign(user_id),  # ← 从 user-profile-service HTTP API 获取
                period="today"
            )
        )
    return {"astro": resp.text, "confidence": resp.score}

🛡️ 关键约束:

  • 各服务独占数据库,禁止任何跨库 JOIN 或直接访问;
  • API网关(Spring Cloud Gateway)配置 Resilience4j 熔断:failureRateThreshold=50%, waitDurationInOpenState=60s
  • Q:旧版前端兼容?A:网关层部署 REST-to-gRPC Adapter,将 GET /v1/reading?user=123 自动转换为 gRPC 调用。

5. 基于画像的智能推荐引擎(协同过滤+规则引擎)

推荐不是“猜你喜欢”,而是可解释、可审计、可纠偏的决策系统。我们采用混合架构:

  • 冷启动期(新用户/低行为):规则引擎主导(Drools 风格 Python 实现);
  • 热启动期(>5次交互):LightFM 协同过滤 + 规则兜底;
  • 所有结果:返回 reason 字段说明依据(如 "water-sign intuition + high engagement")。
def recommend_reading(user_traits: UserTrait) -> list[Recommendation]:
    # 规则引擎:高置信度业务逻辑
    if (user_traits.astro_sign in ["cancer", "scorpio", "pisces"] and
        user_traits.tarot_engagement_score > 70):
        return [Recommendation(
            content_id="emotional_depth_guide",
            reason="Your water-sign intuition + high engagement suggests deeper interpretation",
            is_sponsored=False
        )]
    
    # 回退至协同过滤(特征已标准化)
    return lightfm_recommender.fit_predict(
        user_id=user_traits.user_id,
        user_features=normalize_vector(user_traits.to_vector())
    )

🧩 训练注意事项:

  • LightFM 输入特征向量必须经 MinMaxScaler 归一化(避免星座ID=12 与停留时长=8420 量纲失衡);
  • 所有推荐项强制添加 is_sponsored: bool 字段,便于审计与AB测试;
  • Q:避免推荐偏见?A:在训练日志中注入反事实样本——例如对火象用户,人工注入10%土象内容曝光事件,打破“火象只看火象”的反馈闭环。

6. 隐私与安全加固专项(生产必备)

合规不是“加个隐私政策页”,而是贯穿全链路的技术控制:

  • 前端脱敏:生日输入框提交后,立即转为 age_range: "25-30" 存储,原始值不上传;
  • 传输层:mTLS双向认证,astro-serviceinsight-service 通信必须验证对方证书;
  • 后端权限:Casbin 实现字段级 RBAC,策略文件即代码:
# policy.csv
p, astro_reader, /api/v1/astro/*, GET, allow
p, user:123, /api/v1/user/profile/123, GET, allow
p, admin, /api/v1/user/*/anonymize, DELETE, allow

GDPR 删除流程自动化:
提供 DELETE /api/v1/user/{id}/anonymize 接口,触发后:
① 调用 KMS 解密 PII 表;
② 永久擦除 birthday, phone, email 字段(置空+加盐哈希留痕);
③ 保留 user_id 与匿名行为统计(如“某水象用户平均停留时长”),供产品复盘。

📜 日志规范:禁用 print();所有日志经 structlog 输出为 JSON,包含 user_id, request_id, level, event
📲 数据导出:必须走审批流——调用钉钉审批API发起 【画像数据导出】 流程,审批通过后才生成加密ZIP。

架构全景图:从埋点到推荐的隐私安全闭环

灵性科技的本质,是用严谨工程承载人文温度。当每一次抽牌、每一段解读,都成为理解用户旅程的坐标,我们交付的就不再是“运势”,而是值得信赖的自我探索伙伴。