一款写给猫狗主人的宠物日记 APP — 把"养它的每一天"变成可回看的时间轴。
当当日记 是一个 Flutter + FastAPI 全栈项目,把"养它的每一天"沉淀成可回看的时间轴:拍照、写日常、记体重、登记驱虫疫苗,到期本地推送提醒。
当当日记的核心是协作:一只宠物的档案可以被全家共享。
- 三级角色:OWNER / EDITOR / VIEWER,权限精确到每一个写操作(列表接口直接下发
my_role,前端按角色禁用按钮,不会"点进去才报 403")。 - 扫码即加入:生成带二维码的分享卡片,家人相机扫一扫、或手填 8 位邀请码即可加入。
- 安全可控:邀请码一次性使用、24h 过期、OWNER 可随时撤销;成员可主动退出、亦可被移除。
- 长按语音速记:实时流式 STT + 通义千问意图抽取,一句"奶牛今天 4.2 公斤,吃了驱虫药"自动落成结构化草稿(体重 / 驱虫 / 日常各归各位)。
- 照片自动归档:DashScope 多模态 Embedding + pgvector 相似度检索,上传照片自动指认到对应毛孩子,并随用户的确认/纠正持续学习。
此外还有:到期本地推送提醒、品牌化 Logo & Splash、UI 微动画与骨架屏,以及一套以稳定可用为目标的 MVP 基线(手机号 SMS + JWT、HEIC 转码、EXIF 时间还原、MinIO + Nginx 统一入口)。
| 模块 | 能力 |
|---|---|
| 认证 | 手机号 + 短信验证码登录,首次登录自动注册;JWT 双 Token,支持多设备登出 |
| 宠物档案 | 多档案 CRUD,头像上传,生日/品种/驱虫周期管理 |
| 照片记录 | 单次最多 9 张,单张 ≤ 15 MB;前端 EXIF 提取拍摄日期,HEIC/HEIF 自动转 JPEG |
| 健康管理 | 体重曲线、内/外驱虫、疫苗、日常护理记录;驱虫倒计时与到期提醒 |
| 时间轴 | 沉浸式照片墙 + 多档案筛选 + 滚动条快速定位 |
| 本地推送 | 驱虫到期前 3 天本地通知,零依赖外部推送服务 |
| 档案共享 | OWNER / EDITOR / VIEWER 三级角色;二维码卡片 + 8 位邀请码加入,可主动退出 / 被移除 |
| 语音速记 | 长按说话 → 流式转文字 → LLM 抽槽 → 生成记录草稿 |
| 照片自动归类 | 多模态向量化 + pgvector 相似度,把刚上传的照片自动指认到对应宠物 |
| 品牌化 & UI 打磨 | Logo / Splash / AppBar 统一品牌资产;列表微动画 + 骨架屏加载态 |
| 类别 | 选型 | 用途与说明 |
|---|---|---|
| 框架 | Flutter 3.11+ / Dart |
单代码库覆盖 Android / iOS |
| 状态管理 | flutter_riverpod: ^2.5.0 |
类型安全、可测、解耦 UI 与业务,配合 AsyncValue 处理异步状态 |
| 路由 | go_router: ^14.0.0 |
声明式路由 + 深链跳转,支持登录拦截 |
| 网络 | dio: ^5.7.0 |
拦截器统一注入 JWT、自动续 token;APP 唤醒时主动清空连接池避开 NAT 失效 |
| 本地存储 | shared_preferences: ^2.3.0 |
Token / 偏好持久化 |
| 图像处理 | image_picker: ^1.1.0 / image_picker_android: ^0.8.13(Android 13+ Photo Picker)+ flutter_image_compress: ^2.3.0 + exif: ^3.3.0 |
多选限制真实生效;HEIC → JPEG 端上完成;EXIF 拍摄日期解析 |
| 图片缓存 | cached_network_image: ^3.4.0 + 自定义 PaintingBinding 缓存(512 MiB / 600 entries) |
时间轴缩略图与原图同时常驻,滚动不掉帧 |
| 大图查看 | photo_view: ^0.15.0 |
双指缩放、原图懒加载 |
| 本地推送 | flutter_local_notifications: ^18.0.1 + timezone: ^0.9.4 |
驱虫到期日历调度,无需 FCM / 极光 |
| 语音录制 | record: ^5.1.2 |
长按语音输入 |
| 二维码分享 | qr_flutter: ^4.1.0 + mobile_scanner: ^7.2.0 + saver_gallery: ^4.1.1 |
生成分享二维码卡片、相机扫码加入、卡片 / 原图存相册(扫码 Android 走 MLKit、iOS 走 Apple Vision) |
| UI 打磨 | flutter_animate: ^4.5.2 + shimmer: ^3.0.0 |
列表错峰 / 按钮按压微动画 + 骨架屏加载态 |
| 矢量品牌 | flutter_svg: ^2.0.10+1 |
Logo / Splash / AppBar 复用同一份 SVG |
| 其它 | intl / pull_to_refresh / permission_handler / path_provider / flutter_slidable / uuid |
— |
| 类别 | 选型 | 用途与说明 |
|---|---|---|
| Web 框架 | fastapi==0.115.0 + uvicorn[standard]==0.30.0 |
自动生成 Swagger,原生 async;统一异常处理转 code/message/details |
| ORM | sqlalchemy==2.0.35(async)+ asyncpg |
全异步 DB 访问;AsyncSession 注入 |
| 迁移 | alembic==1.13.0 |
版本化迁移,支持 pgvector 扩展 |
| 数据校验 | pydantic==2.9.0 + pydantic-settings |
强类型请求 / 响应模型;.env 自动加载 |
| 鉴权 | python-jose[cryptography] + passlib[bcrypt] |
JWT 编解码;bcrypt 为头像 / 备用密钥保留 |
| 缓存 | redis==5.1.0 |
短信验证码 (5 min TTL)、60 s 重发冷却、分类结果短期缓存 |
| 对象存储 | minio==7.2.9 |
S3 协议;启动时预创建所有 bucket,省请求路径开销 |
| 文件上传 | python-multipart |
multipart/form-data 解析 |
| 出站 HTTP | httpx==0.27.0 |
调阿里云、DashScope;连接复用 |
| 任务调度 | apscheduler==3.10.4 |
后端定时任务(清理临时文件等) |
| 图像 | pillow==10.4.0 |
缩略图生成、EXIF 解析、上传图片预处理 |
| 阿里云 SDK | alibabacloud-dypnsapi(短信认证)+ alibabacloud-imagerecog(宠物校验,默认关闭) |
SMS 登录;服务端宠物图像校验默认关闭、代码保留待启 |
| AI 服务 | dashscope>=1.20.0 + openai>=1.40.0 |
通义千问 LLM (qwen-flash)、fun-asr 流式 STT、多模态向量化 |
| 向量库 | pgvector>=0.3.0,<0.4 |
PostgreSQL 原生向量索引,照片自动归类的相似度引擎 |
| 测试 | pytest + pytest-asyncio + aiosqlite |
单测跑在内存 SQLite 上,无需起 PG |
| 组件 | 镜像 | 角色 |
|---|---|---|
| Nginx | nginx:1.27-alpine |
真机唯一入口;/api/... → FastAPI、/media/... → MinIO |
| PostgreSQL | pgvector/pgvector:pg16 |
关系数据 + 向量索引(pgvector 扩展) |
| Redis | redis:7-alpine |
验证码、并发限流、分类缓存;启用 AOF |
| MinIO | minio/minio:latest |
S3 兼容对象存储;后期可无痛迁移阿里云 OSS |
| FastAPI | 自建 python:3.11-slim 镜像 |
业务后端 |
- 阿里云号码认证服务(Dypnsapi
SendSmsVerifyCode) — 系统赠送签名 + 模板,验证码由 API 自动生成。 - 阿里云 DashScope — 多区域调度:
- 新加坡区域 跑 STT (
fun-asr-realtime) 与 LLM (qwen-flash),TLS 握手延迟从北京区 6.3 s 降到 2.6 s(实测 N=10)。 - 北京区域 跑多模态向量化 (
tongyi-embedding-vision-plus, 1152 维) 与 STT 兜底。
- 新加坡区域 跑 STT (
下面这些是把当当日记从「demo」推到「真能上手用」的关键设计,每一条都对应着代码里能看到的实现,而不是泛泛而谈。
客户端 只 跟 Nginx (:80/:443) 通信,FastAPI 的 :8000、MinIO 的 :9000 全部隐藏在 Docker 网络里。/api/... 反代给 FastAPI,/media/... 反代给 MinIO,签名 URL 也基于 PUBLIC_BASE_URL 拼装,保证前端拿到的 URL 全部走入口域名 — 既方便上线时换 HTTPS / 更换 OSS,也避免内网地址泄露。
把宠物档案分享给家人只需一步:
- 二维码卡片:
qr_flutter渲染一张带宠物信息的分享卡片,一键存进相册(saver_gallery)发给家人; - 相机扫码加入:
mobile_scanner(Android 走 MLKit、iOS 走 Apple Vision)扫码、或手填 8 位邀请码即可加入; - 安全可控:邀请码一次性使用、24h 过期、OWNER 可随时撤销。
注:早期的「端上 TFLite + 服务端
RecognizeScene宠物校验」自 Optimization Step 1(2026-05)起默认关闭 —— 真实用户常传宠物用品 / 食盆 / 疫苗本等强相关但"非猫狗"的照片,严格分类器反而误拒、伤害体验。相关代码与开关(enableClientPetRecognition/ENABLE_SERVER_PET_RECOGNITION)保留,便于将来按需重启。
iPhone 默认存 HEIC,且很多 Android 厂商修改过 EXIF。我们在 Flutter 端:
- 用
exif包读取DateTimeOriginal,多张图取第一个有效值,没有则回落到当天; - 检测到 HEIC/HEIF 直接
flutter_image_compress转 JPEG 再上传。
后端只负责存储,永远不需要为了"拿到拍摄日期"而二次解码原图。
- Access Token (2 小时) + Refresh Token (30 天),dio 拦截器自动续签;
POST /auth/logout只作废当前设备的 refresh token,其他设备不掉线;- 验证码存 Redis (TTL 5 min, 60 s 重发冷却),杜绝短信轰炸。
不接 FCM、不接极光,纯靠 flutter_local_notifications:
- APP 启动 / 回前台时,从后端拉一次驱虫与疫苗的最新状态;
- 在端上调度未来若干天的本地通知,到期前 3 天开始提醒,过期后每天补提醒;
- 用户记录新驱虫立刻重排调度。
省下推送通道费用 + 用户隐私不必经过第三方服务。
- 邀请码
pet_share_codes表 + 一次性使用 + 主动撤销 + 24 h 过期; pet_members表存成员关系,FastAPI 在每个写接口上校验my_role;- 列表接口的
pet.my_role字段让前端能基于角色精准 disable 按钮,避免"点进去才报 403"。
最有意思的功能之一:上传照片后,后端调用 DashScope 多模态向量化(1152 维)embed 一遍,和该用户每只宠物的 图心 (centroid) 做余弦相似度比较,再用 Top-1 阈值 + margin 规则决定归属。
工程上的几个克制设计:
- 去重窗口:30 天内相似度 ≥ 0.98 的样本不重复入库,避免连拍 10 张同一姿势把图心带偏;
- 来源加权:用户手动纠正过的样本在打分时获得
+0.02的小幅 boost,只在原本就模棱两可的边界上起作用; - 阈值可调:
CLASSIFY_SIM_TOP1_MIN=0.78、CLASSIFY_SIM_MARGIN_MIN=0.05通过.env调; - 缓存:Redis 短期缓存分类结果,重复点击不重复扣费;
- 并发护栏:
DASHSCOPE_EMBEDDING_CONCURRENCY=3,5 张连传不会饿死其他请求路径。
长按录音 → 上传到后端 → DashScope fun-asr-realtime (新加坡区) 流式 STT → 通义千问 qwen-flash 抽槽(实体、日期、记录类型)→ 生成结构化草稿,用户在 sheet 里再确认即可保存。
实测延迟(3.1 s 音频):
| 路径 | p50 | p90 |
|---|---|---|
| 北京 paraformer-v1 | 6.34 s | 7.24 s |
| 北京 fun-asr | 6.28 s | 84.2 s(!) |
| 新加坡 fun-asr (现行) | 2.60 s | 4.33 s |
把 STT 路由切到新加坡区是基于真机 benchmark 的硬决策,而不是凭直觉。
代码里多处藏着"在 2C4G VPS 上不要被自己绊倒"的小修正:
- 手动设置 asyncio 线程池大小 (
THREAD_POOL_SIZE=32):Python 默认在 2 核机上只有 6,单次 5 张照片 classify + 5 张照片 backfill 立刻饿死; - MinIO bucket 启动预创建 (
aensure_all_buckets):请求路径上不再做head_bucket; - APP 唤醒时清空 dio 连接池:手机睡了一夜运营商 NAT 4-tuple 早过期,复用旧 socket 会导致返回前台后第一次 POST 神秘卡住;
- Nginx 自定义 access log:把
request_time/upstream_response_time/request_length全打出来,遇到慢请求一眼定位是上传慢、Nginx 慢还是 FastAPI 慢; assert_production_safe(settings):生产模式下启动前自检密钥、CORS、调试开关,防止把 demo 配置部署上线。
- 所有业务错误:
{ "code": "...", "message": "...", "details": {...} }; - 列表分页:
page+page_size,新→旧排序; - 创建/更新返回最新完整对象、删除
204、空列表200 + []; - Pydantic
RequestValidationError被 FastAPI 异常处理器翻译为业务码(INVALID_PHONE/INVALID_VERIFY_CODE/INVALID_NICKNAME),前端不用再读 Pydantic 的英文报错。
graph TB
subgraph client[Flutter APP]
Android["Android"]
iOS["iOS (Phase 2)"]
LocalPush["本地推送<br/>(flutter_local_notifications)"]
end
subgraph server[云服务器 2C4G - Docker Compose]
Nginx["Nginx :80/:443<br/>统一入口"]
FastAPI["FastAPI<br/>:8000"]
PG["PostgreSQL 16<br/>+ pgvector"]
Redis[("Redis 7")]
MinIO[("MinIO<br/>S3 兼容")]
end
subgraph third[第三方服务]
SMS["阿里云 Dypnsapi<br/>短信认证"]
DSCN["DashScope 北京<br/>LLM + 多模态向量"]
DSSG["DashScope 新加坡<br/>fun-asr STT"]
end
Android -->|HTTPS| Nginx
iOS -->|HTTPS| Nginx
Android --- LocalPush
Nginx -->|/api/| FastAPI
Nginx -->|/media/| MinIO
FastAPI --> PG
FastAPI --> Redis
FastAPI --> MinIO
FastAPI --> SMS
FastAPI --> DSCN
FastAPI --> DSSG
DangDangDiary/
├── backend/ # Python FastAPI 后端
│ ├── app/
│ │ ├── main.py # 入口、异常处理器、生命周期钩子
│ │ ├── config.py # 全部配置 ↔ .env
│ │ ├── database.py # SQLAlchemy 异步 session
│ │ ├── dependencies.py # FastAPI 依赖(鉴权、当前用户)
│ │ ├── api/v1/ # 路由层
│ │ │ ├── auth.py # 短信 + JWT
│ │ │ ├── pets.py # 宠物档案 CRUD
│ │ │ ├── photos.py # 上传 / 时间轴
│ │ │ ├── classify.py # 照片自动归类(多模态向量 + pgvector)
│ │ │ ├── health.py # 体重 / 驱虫 / 疫苗 / 日常
│ │ │ ├── share.py # 档案共享(二维码 / 邀请码 + 角色)
│ │ │ ├── voice.py # 语音速记(STT + LLM 抽槽)
│ │ │ └── router.py # 注册所有子路由
│ │ ├── models/ # SQLAlchemy ORM
│ │ ├── schemas/ # Pydantic 模型
│ │ ├── services/ # 业务逻辑(auth/pet/health/embedding/llm/stt/...)
│ │ ├── tasks/ # 定时任务
│ │ └── utils/ # 工具(security/time/invite_code/production_check)
│ ├── alembic/ # 数据库迁移
│ ├── tests/ # pytest 单测
│ ├── scripts/ # 运维脚本(stt_bench.py / llm_bench.py 等)
│ ├── requirements.txt
│ ├── Dockerfile
│ └── .env.example
├── frontend/ # Flutter 客户端
│ └── lib/
│ ├── main.dart # 入口(cache 调优、Photo Picker 启用)
│ ├── app.dart # 顶层 App + 生命周期 hook
│ ├── config/ # 路由 / 主题 / base_url
│ ├── models/
│ ├── services/ # api_client / voice / health_reminder / share / classify...
│ ├── providers/ # Riverpod
│ ├── screens/
│ │ ├── auth/ # 登录
│ │ ├── record/ # 拍照记录
│ │ ├── health/ # 体重/驱虫/疫苗/日常 4 个 tab
│ │ ├── timeline/ # 时间轴 + 大图
│ │ ├── profile/ # 我的 / 宠物管理 / 档案共享
│ │ └── splash/ # 启动屏
│ ├── widgets/ # 复用组件(语音按钮 / 沉浸式照片格 / 品牌 Logo...)
│ └── utils/
├── nginx/nginx.conf # 统一入口反代
├── docker-compose.yml # PG / Redis / MinIO / Nginx 一键起
├── docs/ # 步骤文档(开发圣经)
└── README.md # 本文件
cp backend/.env.example backend/.env
# 然后填入:
# - 数据库密码、JWT 随机串
# - 阿里云 AccessKey(短信 + 场景识别共用)
# - DashScope 双 Key(北京 + 新加坡)docker compose up -d # FastAPI + PG (pgvector) + Redis + MinIO + Nginx后端(FastAPI)已收进 compose:build: ./backend,绑定挂载源码 + --reload,
改业务代码自动热重载,无需重启;只有改 requirements.txt 才要重建镜像。
# 数据库迁移(.env 用 compose 服务名,必须在容器里跑)
docker compose exec fastapi alembic upgrade head
# 改了 .env 后重读配置
docker compose restart fastapi
# 改了 requirements.txt 后重建
docker compose build fastapi && docker compose up -d fastapi
# Swagger: https://<域名>/docs(走 Nginx),或本机 http://localhost:8000/docs起停、迁移、换域名、媒体签名等运维细节与踩坑见
docs/deploy-ops.md。
cd frontend
flutter pub get
flutter run # 真机调试
flutter build apk --release # 出包cd backend && pytest -v # 跑在内存 SQLite,无需 PG- 字段全部
snake_case - 列表分页:
page+page_size,新→旧排序,业务语义 key (pets/photos/weights) - 时间戳 UTC;生日、拍摄日、记录日用
date - 错误响应:
{ "code", "message", "details" };输入非法400、无权限403 - 创建/更新返回最新完整对象;删除
204 No Content;空列表200 + [] - 客户端只能访问 Nginx 入口;
/api/...与/media/...之外的内部地址永远不出现在响应里
完整 API 列表见 docs/DangDangDiary-technical-plan.plan.md §五。
| 文档 | 作用 |
|---|---|
docs/00-global-rules.md |
跨步骤约定(最高优先级) |
docs/DangDangDiary-technical-plan.plan.md |
总架构、DB、API 全览 |
docs/step1-environment-setup.md |
环境与项目骨架 |
docs/deploy-ops.md |
部署与运维:起停 / 重启 / 迁移 / 换域名 / 媒体签名踩坑 |
docs/step2-auth-module.md |
短信 + JWT |
docs/step3-pet-profile.md |
宠物档案 CRUD |
docs/step4-photo-record.md |
照片上传 + EXIF + 校验 |
docs/step5-health-management.md |
体重 / 驱虫 / 疫苗 |
docs/step6-timeline.md |
时间轴 |
docs/step7-push-notification.md |
本地推送 |
docs/step8-integration-polish.md |
鲁棒性 + 自动化测试 |
docs/phase2-step1-pet-share.md |
档案共享 |
docs/phase2-step2-voice-intake.md |
语音速记 |
docs/phase2-step3-photo-auto-assign.md |
照片自动归类 |
docs/phase2-step4-logo-splash.md |
品牌资产 + Splash |
docs/future-async-task-queue.md |
未来:异步任务队列 |
docs/future-voice-frontend-streaming.md |
未来:前端 PCM 流式上传 |
docs/API_docs/ |
阿里云 / DashScope 第三方接口参考 |
本项目当前为内部开发阶段,暂未公开许可证。如需复用代码或素材请先联系作者。
