系统架构
只读盘点快照
本文档为 inklet 技术栈的只读盘点快照,更新日期 2026-07-13。它记录当时代码库的实际状态,供团队共享与新人 onboarding,不应被当作实时事实——版本升级或组件调整后需重新盘点更新。
inklet 是一个云连接的电子墨水屏信息面板平台:后端把结构化数据渲染成 800×480 的黑白/4 级灰点阵图,经 AWS IoT Core 推送到墨水屏设备显示。仓库是一个 多组件 monorepo:Go 后端 + Python 渲染/AI worker + React 前端 + 设备端(Python)/模拟器(Node) + AWS IoT 基建脚本。
核心链路一句话:结构化数据 → render_task/SQS → Jinja2/WeasyPrint 渲染 → Floyd–Steinberg 抖动打包 → S3 → MQTT 通知设备拉取显示。
系统概览
┌──────────────┐ HTTPS ┌──────────────┐ PostgreSQL ┌─────────┐
│ portal-web │ ──────────────▶│ backend │ ──────────────────▶│ RDS │
│ (React SPA) │ │ (Go / Chi) │ └─────────┘
└──────────────┘ └──┬────────┬──┘
│ SQS │ MQTT (X.509 mTLS)
┌────────┴───┐ ┌───┴────────┐
│ worker │ │ AWS IoT │
│ (Python) │ │ Core │
│ 渲染/AI │ └───┬────────┘
└────┬───────┘ │ MQTT (X.509 mTLS)
│ S3 ┌───┴────────┐
┌────┴───┐ │ e-ink │
│ S3 │◀────│ devices │
└────────┘拉取 └────────────┘
顶层仓库结构
| 目录 / 仓库 |
角色 |
语言 / 栈 |
用途 |
backend/ |
服务端 API |
Go 1.24 / chi / GORM |
设备接入、认证、推送、render_task 生命周期、计费 |
worker/ |
后台 worker 群 |
Python 3.12 |
渲染管线 + AI 摘要/视觉 + 内容分析 Agent + 抓取 |
html_template/ |
模板库 |
HTML + Jinja2 + CSS |
templates/<slug>/{config.json, template.html, schema.json} |
portal-web/ |
Web 前端 + 部署编排 |
React 19 / Vite / TS |
用户控制台 + docker-compose/nginx/caddy/certbot |
eink-hw/(即 sim-hw) |
设备端固件(模拟) |
Python 3.11 |
一进程=一台设备,AWS IoT provisioning + 心跳 + 显示 |
eink-sim/(即 sim-dashboard) |
墨水屏模拟器 |
Node/TS Fastify + React |
把 1bpp 帧缓冲在浏览器里渲染成墨水屏效果 |
awsiot/ |
AWS IoT 基建脚本 |
Bash + JSON |
Fleet Provisioning by Claim、策略、claim 证书 |
fake-backend/ |
本地云端 mock |
Python 3.11 |
订阅心跳→回下行命令,测端到端闭环 |
InkletDemo/ |
3D 空间演示 |
React / Three.js |
房间 3D 模型里可视化设备安放位置(非核心链路) |
docs/ |
文档站 |
MkDocs (Material) |
架构 / API / 运维文档(本文档所在) |
后端(Go)— backend/
- 语言 / 框架:Go 1.24,HTTP 路由 chi/v5(+ go-chi/cors),标准
net/http,端口 4000。入口 cmd/server/main.go。
- 数据库:PostgreSQL 16,ORM 用 GORM(
gorm.io/driver/postgres,底层 pgx v5),迁移靠 GORM AutoMigrate + 少量裸 SQL 建索引。主要表:users / oauth_accounts / sessions / subscriptions / devices / device_commands / files / raw_upload_items / html_templates / pushes / render_tasks / content_blocks。
- 队列:AWS SQS(自封装
queue.SQSQueue),四条队列:SQS_PREPARE_URL(item 准备)/ SQS_RENDER_URL(渲染)/ SQS_ANALYZER_URL(内容分析)/ SQS_SUMMARY_URL。render 流程:后端建 render_task(READY) → 投 render 队列 → worker 渲染完回调 POST /internal/pushes/{id}/rendered → 后端把 push 置为设备当前并经 MQTT 通知设备。
- 对象存储:AWS S3(aws-sdk-go-v2),预签名上传/下载 URL,可选 CloudFront 签名(RSA)。桶由
S3_BUCKET / S3_REGION / S3_ENDPOINT(可指 minio)配。
- IoT / 设备:MQTT 3.1.1 over TLS(mTLS X.509),库
eclipse/paho.mqtt.golang,接 AWS IoT Core。30s 一次离线检查(2min 阈值)。设备绑定三路:NFC 签名 / 6 位 claim code(10min) / NFC 上的 JWT。
- 对外 API:REST/JSON,JWT(HS256, golang-jwt) Bearer 鉴权 + OAuth(Google/Apple)。主要路由组:
/auth(注册登录刷新 / OAuth / me / sessions)、/auth/subscription(Stripe 订阅/结账/门户/发票 + webhook)、/api/devices(设备/健康/状态/昵称/绑定解绑/命令/push 队列/自定义图上传)、/api/raw-items、/api/files/{id}/download、/api/analyze/trigger、/internal/*(worker 回调,X-Internal-Token)。
- internal/ 分包:
auth / billing / config / database / file / iot / item / middleware / model / pkg / queue / storage。
MQTT 主题结构
| 主题 |
方向 |
用途 |
inklet/dev/{thing}/up/heartbeat |
设备 → 后端 |
定时心跳 |
inklet/dev/{thing}/up/state |
设备 → 后端 |
设备状态上报 |
inklet/dev/{thing}/up/request_claim |
设备 → 后端 |
请求配对码 |
inklet/dev/{thing}/down/cmd |
后端 → 设备 |
命令下发(text、claim_code、bound、unbound) |
渲染 worker(Python)— worker/
- 打包:Python 3.12(pyproject/setuptools),入口
inklet-worker(workers/cli.py)。多模式单进程:render / summary / download / analyze,SQS 轮询 + ThreadPool 并发。Docker:python:3.12-slim + Pango/Cairo/gdk-pixbuf + fonts-dejavu-core(无 CJK 字体)。
- 渲染管线(
workers/render_worker.py):取 render_task(JOIN html_templates + pushes)→ 若 CUSTOM_IMAGE 走 S3 下载,否则用 config.title 字符串匹配定位模板目录 → Jinja2(autoescape=False) 渲染 → WeasyPrint HTML→PDF → PyMuPDF(fitz) 栅格化(96dpi)PNG → Pillow 缩放 800×480(LANCZOS) → 灰度 → Floyd–Steinberg 抖动 → 打 1bpp(image2.raw) + 2bpp(4 级灰 image4.raw) + png → S3 render/{user}/{device}/{push}/ → 写 files + 更新 pushes/render_tasks。
- AI 侧:
summary_worker(文本摘要 + gpt-4o 视觉描述,输出 JSON)/ download_worker(firecrawl 抓链接)/ analyze_worker(OpenAI Agents SDK 智能体循环,工具读 notes/templates/devices 并 create_push)。LLM 统一走 litellm(默认 OpenAI,可切 Anthropic),模型由 LLM_MODEL / LLM_VISION_MODEL / AGENT_MODEL 配;prompts 在 worker/prompts/。
- 与后端共享同一 PostgreSQL(psycopg2,
DATABASE_URL)和 SQS 队列。
Web 前端 — portal-web/portal-web/
- 框架:React 19 + TypeScript + Vite 6,样式 Tailwind CSS 4,路由 react-router 7,toast 用 sonner,包管理 pnpm。
- 功能:设备/显示屏管理(列表/详情/昵称)、推送内容队列、Knowledge(AI 摘要的知识条目)、订阅计费、账户/会话/OAuth 回调、明暗主题。
- 构建/部署:多阶段 Dockerfile(Node 22 pnpm build → Nginx Alpine,端口 3000)。域名
portal.iminklet.com→3000、auth.iminklet.com→4000(Caddy/Nginx 反代 + Let's Encrypt certbot)。
- 另有
eink-sim(墨水屏模拟器前端)和 InkletDemo(Three.js 3D 演示)两个独立 Vite/React 应用,属工具/演示,非用户控制台。
设备 / 固件侧 — eink-hw/(模拟实现)
- Python 3.11,依赖
awsiotsdk/awscrt(MQTT) + Pillow。一进程模拟一台 800×480 1bpp 墨水屏;首启走 AWS IoT Fleet Provisioning by Claim 拿永久证书,稳态循环发心跳(30s)、收下行命令、渲染文字并 HTTP POST 帧缓冲(48000B) 给 eink-sim。
- 协议:对云 MQTT(AWS IoT Core);对模拟器 HTTP/WebSocket。真实硬件面板目标 800×480 单色。
awsiot/ 是 Bash 脚本一次性建好 IoT 角色/策略/provisioning 模板/claim 证书。
设备生命周期
出厂 → NFC 标签写入签名后的 hw_id
↓
开机 → Fleet Provisioning(claim 证书 → 设备证书)
↓
心跳循环 → 后端在数据库中注册设备
↓
用户绑定设备(NFC 或配对码)
↓
后端下发命令 → 设备渲染到显示屏
部署与运行
- docker-compose(
portal-web/docker-compose*.yml):postgres:16 + backend(ghcr.io/inklet-2026/backend) + portal-web;prod 版加内网、挂密钥、仅绑 127.0.0.1、restart 策略。
- 外部服务:PostgreSQL(dev 容器 / prod RDS)、AWS S3(+CloudFront)、AWS SQS、AWS IoT Core(MQTT)、OAuth(Google/Apple)、Stripe(计费)、OpenAI/Anthropic(LLM)、Firecrawl(抓取)。
- CI/CD:GitHub Actions
release.yml(backend/worker/portal-web 各一份),打 v* tag 触发 → 构建/测试 → 推 ghcr.io/inklet-2026 镜像(version + latest)。无强制 pre-merge 检查,发布走手动 tag。
- 部署详见
portal-web/DEPLOY.md,本地联调见 RUNBOOK.md。
已知约束与风险点
- WeasyPrint 引擎:CSS 2.1+,无 JS,flex/grid 支持不完整——模板需按此约束写。
- 容器无 CJK 字体:worker 镜像只装 DejaVu,中文会渲成豆腐块(影响中文内容渲染,另行处理)。
- 画布固定 800×480:溢出即裁,模板必须按此尺寸设计。
- 发布无强制 pre-merge 检查:走手动
v* tag,缺门禁——合并前质量靠人工把关。
关键依赖版本表
以下为盘点时(2026-07-13)从 go.mod / pyproject.toml / package.json / Dockerfile 直接读取的当前锁定版本,供 reviewer 对齐,不穷举传递依赖。
后端 backend/(Go)
| 组件 / 库 |
版本 |
| Go |
1.24.0 |
| go-chi/chi |
v5.2.1 |
| gorm.io/gorm |
v1.25.12 |
| gorm.io/driver/postgres |
v1.5.11 |
| jackc/pgx |
v5.5.5(indirect) |
| aws-sdk-go-v2(core) |
v1.41.7 |
| aws-sdk-go-v2/service/s3 |
v1.101.0 |
| aws-sdk-go-v2/service/sqs |
v1.42.27 |
| eclipse/paho.mqtt.golang |
v1.5.1 |
| golang-jwt/jwt |
v5.2.2 |
| stripe/stripe-go |
v82.1.0 |
| golang.org/x/oauth2 |
v0.29.0 |
| PostgreSQL(运行时) |
16 |
渲染 worker worker/(Python)
| 组件 / 库 |
版本 |
| Python(运行时镜像) |
3.12-slim(requires-python >=3.11) |
| Jinja2 |
>=3.1 |
| Pillow |
>=10.0 |
| WeasyPrint |
未固定(Dockerfile pip install weasyprint,取最新) |
| PyMuPDF |
未固定(Dockerfile pip install pymupdf,取最新) |
| boto3 |
>=1.35 |
| psycopg2-binary |
>=2.9 |
| litellm |
>=1.40 |
| openai-agents |
>=0.17.3 |
| firecrawl-py |
>=1.0 |
| cryptography |
>=43 |
Web 前端 portal-web/portal-web/(React)
| 组件 / 库 |
版本 |
| react / react-dom |
^19.2.4 |
| vite |
^6.3.5 |
| tailwindcss / @tailwindcss/vite |
^4 |
| react-router-dom |
^7.6.1 |
| typescript |
^5 |
| sonner |
^2.0.7 |
设备端 eink-hw/(Python)与模拟器 eink-sim/(Node)
| 组件 / 库 |
版本 |
| eink-hw · Python |
>=3.11 |
| eink-hw · awsiotsdk |
>=1.21.0 |
| eink-hw · awscrt |
>=0.20.0 |
| eink-hw · Pillow |
>=10.0 |
| eink-sim · fastify |
^5.1.0 |
| eink-sim · @fastify/websocket |
^11.0.1 |
| eink-sim · pngjs |
^7.0.0 |
| eink-sim · react / react-dom |
^19.0.0 |
| eink-sim · vite |
^7.0.0 |
| eink-sim · typescript |
^5.7.2 |
各角色该掌握的栈
- 后端工程师:Go 1.24 / chi / GORM+Postgres / aws-sdk-go-v2(S3·SQS) / paho-MQTT(AWS IoT) / JWT+OAuth / Stripe。核心在
backend/internal/{iot,queue,storage,billing,auth}。
- 渲染 worker 工程师:Python 3.12 / Jinja2 / WeasyPrint / PyMuPDF / Pillow(抖动 · 1bpp/2bpp)/ SQS(boto3) / psycopg2;AI 部分 litellm / OpenAI Agents / firecrawl。牢记 WeasyPrint 限制 + 容器无 CJK 字体 + 画布固定 800×480。
- 前端工程师:React 19 / TS / Vite / Tailwind4 / react-router;对接后端 REST + JWT。
- 设备 / 固件工程师:Python(awsiotsdk) + AWS IoT Fleet Provisioning + MQTT 主题约定;模拟器侧 Node/Fastify + React + WebSocket(1bpp 帧协议)。
- 运维:docker-compose + Nginx/Caddy + certbot;AWS(IoT/S3/SQS/CloudFront)、RDS Postgres;GHCR + tag 触发的 GitHub Actions;
awsiot/ 基建脚本。
- Reviewer / 新人:先读本文档 +
RUNBOOK.md + portal-web/DEPLOY.md,核心链路见文首一句话。