Skip to content
hcidfyPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

DatingApp

海外交友 App 后端。整个系统围绕一个核心命题展开:

真人(BH, BioHuman)与数字人(DH, DigitalHuman)共存。 真人供给不足时,由 AI 数字人承接冷启动阶段的陪伴;两条链路共用同一套消息、社交关系与付费体系。

因此这个仓库里同时存在两件通常不会放在一起的东西:一套 7 服务的 Java 微服务后端,和一个把 LLM 当"演员"用的对话引擎(Python 原型 → Java 重写两代)。

534 个 Java 源文件 · 7 个微服务 + 1 个公共库 · 7 个 gRPC 契约模块 · 8 份 .proto · 2 代数字人引擎

一、系统全景

                     ┌────────────────────────┐
   App / H5  ──REST──►  mobile-gateway (BFF) │  鉴权 / 限流 / 聚合 / 协议转换
                     └───────┬────────────────┘
                             │ gRPC(全系统唯一的东西向协议)
        ┌────────────┬───────┼────────────┬─────────────┐
        ▼            ▼       ▼            ▼             ▼
   user-service  match-service  post-service  payment-service
        ▲            ▲            ▲             ▲
        └────────────┴─────┬──────┴─────────────┘
                           │
                    im-service  ──►  ai-chat-v2 (Java)   数字人引擎
                       │      └──►  ai-chat    (Python) 原型
                       │
                  OpenIM / LiveKit(IM 与音视频 provider,只有 im-service 直连)
服务 职责 关键机制
mobile-gateway 对外唯一入口(REST BFF),聚合下游 gRPC JWT 签发/校验 + refresh + 黑名单、IP 白名单、限流、TraceId 透传
user-service 身份(手机/三方/设备)与资料、兴趣标签、附近的人、DH 候选池 多路 ResolveOrCreate、头像 presigned 直传、地理字典(US Cities 种子数据)
match-service 每日卡片队列、滑卡、匹配、访客/喜欢记录、配额 队列按在线/离线分别生成、Ranker + PreferenceBuilder、Outbox 最终一致
post-service 动态发布、评论、点赞、Feed 写扩散到 MQ、Redis 计数与 Feed 池、ShedLock 定时落库/打分
payment-service 订单、订阅、金币账本、提现 PayPal 执行器、REST + gRPC 双协议、幂等账本
im-service 消息路由、在线状态、出站通知、DH 打字机 provider 适配层(OpenIM / TencentIM)、事件分发、金币扣费、反导流检测
dating-common 横切公共库 业务主键生成器(含 DB 序列)、S3 对象存储与 presigned、告警通道(企业微信 + 令牌桶限流 + logback appender)
ai-chat-v2 Java 版数字人引擎(6 个 Maven 模块) Agent = LLM + Loop + Tools,ArchUnit 守护模块边界
ai-chat Python 版数字人引擎(原型) LangChain / LangGraph、SmartRouter 多模态路由与熔断
proto/ gRPC 契约,按服务拆模块 每个服务一个 jar,可独立发版;Java + Python 双份 stub

二、数字人引擎:把 LLM 约束成一个"演员"

这是整个项目里设计密度最高的部分。产品要求 DH 的回复必须像真人,于是引擎的写法是反直觉的:

Agent = LLM + Loop + Tools,循环里零业务逻辑。

im-service ──gRPC ChatAgent/Chat──► ChatAgentGrpcService
                                          │
                                    LaneManager          同会话串行 + 连发合并 + 运行中可打断
                                          │
                                    AgentRuntime         pre-hooks → loop → post-hooks
                                          │
                                     AgentLoop           纯 LLM + function-calling
                                          │
                                    send_message         唯一产出用户可见消息的出口

三条不变量决定了代码为什么长这样:

  1. LLM 的文本输出 ≠ 用户消息。 只有 send_message 工具(COMMIT 阶段)能产出可见消息;模型不调它,DH 就沉默。这条把"说什么"和"要不要说"彻底解耦。
  2. provider 原始报文逐字回传。 剥离或重排 rawPart 会打断跨轮次的 function-calling —— 所以模型报文不做任何规范化。
  3. 循环内零业务逻辑。 要加行为只能加 Hook 或 Tool,AgentLoop 本身不生长。

配套的工程约束:

  • 模块边界由 ArchUnit 编译期守护(6 条规则),aichat-app 是唯一 composition root;依赖方向写错,mvn test 直接红。
  • 同会话串行、跨会话并行:LaneManager 把并发模型收在一处,连发消息在阅读窗内合并成一次 LLM 调用,被合并的那次 gRPC 返回空内容,由最后一次返回完整回复。
  • 安全流水线分层:正则闸(自曝 AI 身份 / 外泄联系方式)→ 分类器 → 分段投递 → 出口守卫;分类器不可用时按 fail-closed 处理。
  • 可观测:每一轮 run 都能按 traceId 追出 hook 顺序、工具调用、provider 选择。

Python 版(ai-chat/)是同一命题的前一代实现:LangGraph 编排 + checkpointer 持久化,VisionAgent 走 SmartRouter 在多家多模态 provider 之间轮询并熔断回退。两代并存是刻意的 —— v2 的很多设计决定就是照着 v1 在真实流量里暴露的问题改的。


三、几个值得单独讲的设计

契约先行:.proto 是唯一真相

proto/ 是一个多模块工程,每个服务一个独立 jar,而不是打一个"全宇宙"包 —— 避免 gateway 拉进它根本用不到的契约。Java 侧由 protobuf-maven-plugin 生成,Python 侧由 gen-python.sh 生成,两侧 schema 同源。

服务之间没有任何共享 DTO:跨进程只认 proto 生成的类型,REST 层的 VO/DTO 只存在于 gateway。

只走 gRPC,且不跨库

服务间不出现 HTTP/Feign/RestTemplate 互调,也不允许连别人家的 DB / Redis key / 对象桶。要数据就调对方 gRPC。

这条约束落到代码上有几个具体后果:

  • ObjectStorage 接口刻意不暴露 bucket 入参 —— 从签名层面堵死"直读别人家桶";跨服务的对象一律由 owner service 自己读、自己签。
  • 对象存储只回 *_key 不回完整 URL,URL 在读取时现签。
  • 跨表不做 JOIN:持久层单表,跨表在 service 层多次单表查 + 内存拼装。

写扩散与最终一致(post / match)

  • post:CreatePost 落库后把扩散动作丢进 RocketMQ,消费端做 Feed 写扩散;Redis 里维护计数增量与 Feed 池,再由 ShedLock 保证多实例只有一个节点跑定时落库/打分任务。Producer 自己带有限次重试,客户端内置重试关掉,避免重试叠加;失败转 DLQ。
  • match:匹配结果的 IM 副作用不放进同一个事务,走 Outbox 表 + 重试任务,代价是几秒延迟,换来的是不跨服务开事务。

IM 与音视频能力收口

业务服务不直连 OpenIM / LiveKit,全部经 im-service。im-service 内部再用 ImProviderAdaptor 抽象把 provider 隔离(OpenIM / TencentIM 两套实现),上层 handler 只处理事件语义:回调分发 → 反导流检测 → 金币扣费 → AI 回复派发 → 在线状态维护。

这样换 IM 供应商的影响面被限制在一个 adaptor 里。

时间与主键

  • 时间一律 UTC:DB 列 TIMESTAMPTZ、JVM TZ=UTC、连接 session 强制 UTC,三层对齐。
  • 业务主键用带语义的 BIGINT 而非自增 id:环境位 + 日期 + 当天序号,由 dating-common 统一生成,数据库序列兜底。自增 id 只做内部物理主键,不出现在任何 API 出参里。

四、技术栈

层 选型
语言 / 运行时 Java 21(虚拟线程)、Python 3.13
框架 Spring Boot 3.3.5、MyBatis-Plus 3.5、Spring Security(gateway)
关系库 PostgreSQL + Flyway(唯一关系库,时间列一律 TIMESTAMPTZ)
缓存 / 分布式锁 Redis + Redisson 3.36(所有 key 带前缀 + TTL)
消息 RocketMQ(rocketmq-spring 2.3;异步事件 / 写扩散 / 解耦)
服务间 gRPC 1.68 + Protobuf 4.28
配置与服务发现 Nacos(Config + Discovery 同 namespace)
对象存储 S3 兼容;dating-common 提供 ObjectStorage 抽象与 presigned 能力
分布式调度 ShedLock 5.16
LLM / 多模态 DeepSeek(对话)、Groq / Gemini / Z.AI(视觉,多 provider 路由 + 熔断)
编排 LangChain / LangGraph(Python 版)
边界守护 ArchUnit

五、目录结构

.
├── dating-server/              # 7 个 Java 微服务 + 公共库(各自独立 Maven 构建)
│   ├── dating-common/          #   业务主键 / 对象存储 / 告警
│   ├── mobile-gateway/         #   REST BFF(对外唯一入口)
│   ├── user-service/           #   身份 / 资料 / 推荐
│   ├── match-service/          #   卡片队列 / 匹配
│   ├── post-service/           #   动态 / Feed
│   ├── payment-service/        #   订单 / 订阅 / 金币
│   └── im-service/             #   消息 / 在线状态 / 通知
├── ai-chat-v2/                 # Java 数字人引擎(6 个模块,ArchUnit 守护边界)
│   ├── aichat-models/          #   领域模型,无框架依赖
│   ├── aichat-core/            #   AgentLoop / Runtime / LaneManager + 端口接口
│   ├── aichat-llm/             #   LLM client + 可 hedge 的路由
│   ├── aichat-infra/           #   Redis 历史与 trace、下游 gRPC 适配
│   ├── aichat-features/        #   Hook / Tool 实现
│   └── aichat-app/             #   Spring Boot 装配(唯一 composition root)
├── ai-chat/                    # Python 数字人引擎(LangChain/LangGraph 原型)
├── proto/                      # gRPC 契约,按服务拆模块,Java + Python 双份 stub
├── nacos/                      # 各服务的配置模板(真值不入库)
└── deploy/                     # 本仓库全部服务的 dev 编排

六、关键取舍

决策 换来什么 代价
服务间只走 gRPC,禁 HTTP 互调 契约强类型、可独立演进、性能可预期 调试链路比 REST 长,需要 grpcurl / reflection
契约按服务拆 jar 调用方只拉需要的依赖 契约发版次数变多
持久层禁多表 JOIN 表可独立分库、写入路径可预测 复杂查询要在内存里拼
时间全链路 UTC 跨机房 / 跨时区不会串天 展示层必须显式做时区转换
LLM 循环内零业务逻辑 行为可插拔、循环稳定 Hook 顺序本身变成一种需要被守护的契约
数字人引擎从 Python 重写到 Java 与 Java 侧共用一套部署 / 观测 / 依赖治理 一次完整重写的成本

七、本地运行

依赖 JDK 21、Maven 3.9+、Docker(Compose v2)。中间件(PostgreSQL / Redis / Nacos / MinIO / RocketMQ)与各服务的连接信息全部走配置模板,仓库里不含任何真实凭据。

# 1. 契约装到本地仓库(各服务的 gRPC 依赖来自这里)
cd proto && mvn -DskipTests install

# 2. 公共库装到本地仓库(user-service 依赖它)
cd ../dating-server/dating-common && mvn -DskipTests install

# 3. 其余服务各自独立构建(dating-server 下没有 parent pom,每个服务自成一个 Maven 工程)
cd ../user-service && mvn -DskipTests package

# 4. 把 nacos/ 下的配置模板贴到 Nacos 对应 namespace,替换其中的占位符
#    (模板里出现 infra.example.com / your_* 的地方都是待填值)

# 5. 复制环境变量模板并填值
cp deploy/.env.deploy.example deploy/.env.deploy

# 6. 起服务
docker network create dating-app
docker compose -f deploy/docker-compose.dev.yml up -d --build

各服务的默认端口见 deploy/docker-compose.dev.yml;健康检查统一走 /actuator/health。


八、模块文档


九、License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages