Files
banban/docs/architecture/index.html
2026-07-07 16:58:05 +08:00

601 lines
19 KiB
HTML

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Banban Architecture Diagrams</title>
<style>
:root {
color-scheme: light;
--bg: #f6f7f9;
--surface: #ffffff;
--text: #1f2933;
--muted: #6b7280;
--border: #d9dee7;
--accent: #2563eb;
--accent-soft: #e8f0ff;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
"Hiragino Sans GB", "Microsoft YaHei", sans-serif;
background: var(--bg);
color: var(--text);
}
header {
position: sticky;
top: 0;
z-index: 10;
border-bottom: 1px solid var(--border);
background: rgba(255, 255, 255, 0.94);
backdrop-filter: blur(10px);
}
.header-inner {
max-width: 1280px;
margin: 0 auto;
padding: 16px 24px 12px;
}
h1 {
margin: 0 0 10px;
font-size: 22px;
line-height: 1.25;
}
.subtitle {
margin: 0;
color: var(--muted);
font-size: 13px;
}
nav {
display: flex;
gap: 8px;
margin-top: 14px;
overflow-x: auto;
padding-bottom: 2px;
}
nav a {
flex: 0 0 auto;
border: 1px solid var(--border);
border-radius: 6px;
padding: 7px 10px;
color: var(--text);
background: var(--surface);
font-size: 13px;
text-decoration: none;
}
nav a:hover {
border-color: var(--accent);
color: var(--accent);
}
main {
max-width: 1280px;
margin: 0 auto;
padding: 20px 24px 48px;
}
section {
margin: 0 0 24px;
padding: 18px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--surface);
}
h2 {
margin: 0 0 6px;
font-size: 18px;
}
h3 {
margin: 22px 0 10px;
color: var(--muted);
font-size: 14px;
font-weight: 650;
}
.section-note {
margin: 0 0 12px;
color: var(--muted);
font-size: 13px;
}
.diagram {
overflow: auto;
width: 100%;
margin: 0;
padding: 14px;
border: 1px solid var(--border);
border-radius: 8px;
background: #fbfcfe;
}
.diagram + .diagram {
margin-top: 16px;
}
.source {
margin-top: 14px;
padding: 10px 12px;
border-radius: 6px;
background: var(--accent-soft);
color: #1e3a8a;
font-size: 13px;
line-height: 1.7;
}
.source code {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 12px;
}
.fallback {
margin: 12px 0 0;
color: var(--muted);
font-size: 13px;
}
@media (max-width: 760px) {
.header-inner,
main {
padding-left: 14px;
padding-right: 14px;
}
section {
padding: 14px;
}
}
</style>
</head>
<body>
<header>
<div class="header-inner">
<h1>Banban Architecture Diagrams</h1>
<p class="subtitle">
Rendered Mermaid viewer for the architecture docs. Requires internet
access for the Mermaid CDN when opened as a local file.
</p>
<nav aria-label="Architecture sections">
<a href="#overview">System Overview</a>
<a href="#bootstrap">Device Bootstrap</a>
<a href="#binding">Binding</a>
<a href="#voice">Voice / IM</a>
<a href="#location">Location</a>
<a href="#alarm">Alarm / Notification</a>
<a href="#deployment">Deployment</a>
</nav>
</div>
</header>
<main>
<section id="overview">
<h2>01 System Overview</h2>
<p class="section-note">Public edge, mini-program, backend, database, MQTT, device, and external providers.</p>
<h3>Runtime Architecture</h3>
<pre class="mermaid diagram">
flowchart LR
subgraph WeChat["WeChat Ecosystem"]
Mini["banban-mini\nTaro WeChat mini-program"]
WxLogin["WeChat login / phone APIs"]
WxMP["WeChat Official Account\nnotifications and bind"]
end
subgraph Edge["Public Edge"]
Domain["banban.api.talkingq.com"]
Nginx["Nginx / TLS"]
end
subgraph Backend["talkingq-url FastAPI backend"]
Main["main.py\nFastAPI app + lifespan"]
AuthMW["banban auth middleware"]
BanbanRouter["banban routers\nchildren, devices, binding, chat,\nlocation, alarms, firmware, roles,\nwechat-mp"]
MqttSvc["TalkingQMQTTService"]
Scheduler["TaskScheduler\ncleanup and periodic tasks"]
StaticAssets["/assets static files"]
end
subgraph Data["Stateful Services"]
MySQL["MySQL\nshared banban schema"]
COS["Tencent COS\nmessage audio files"]
MQTT["MQTT broker"]
end
subgraph DeviceSide["Device Side"]
Device["TalkingQ device"]
NFC["NFC card"]
end
subgraph External["External Providers"]
MapSvc["Map / reverse geocoding provider"]
SMS["SMS provider"]
end
Mini -->|HTTPS /banban/*| Domain --> Nginx --> Main
Main --> AuthMW --> BanbanRouter
Main --> StaticAssets
Main --> Scheduler
Main --> MqttSvc
BanbanRouter --> MySQL
MqttSvc --> MQTT
Device -->|device/{id}/event,response| MQTT
MQTT -->|subscribed messages| MqttSvc
MqttSvc --> MySQL
Device --> NFC
BanbanRouter --> COS
MqttSvc --> COS
BanbanRouter --> WxLogin
BanbanRouter --> WxMP
BanbanRouter --> SMS
MqttSvc --> MapSvc
</pre>
<div class="source">Source: <code>talkingq-url/main.py</code>, <code>banban-mini/src/services/*</code>, <code>database/talkingq_shared_schema.sql</code></div>
</section>
<section id="bootstrap">
<h2>02 Device Bootstrap</h2>
<p class="section-note">How IMEI mappings become active Banban device identities.</p>
<h3>Bootstrap Flow</h3>
<pre class="mermaid diagram">
sequenceDiagram
autonumber
participant Factory as Factory Excel / import CSV
participant Mapping as device_imei_mapping
participant Device as TalkingQ device
participant MQTT as MQTT broker
participant Handler as TalkingQMQTTService
participant Init as device_identity_initializer
participant Auth as device_auth
Factory->>Mapping: Insert imei, device_id, serial_number, status=pending
Device->>MQTT: Publish device/{imei}/event msg_id=012
MQTT->>Handler: Deliver identity init event
Handler->>Init: initialize_by_imei(imei)
Init->>Mapping: SELECT mapping by imei
alt mapping missing or incomplete
Init-->>Handler: DeviceIdentityInitializationError
Handler-->>MQTT: device/{imei}/event_resp status=failed
else mapping found
Init->>Auth: SELECT device_auth by device_id
alt no auth row
Init->>Auth: INSERT device_id, serial_number, is_active=1
else auth row exists and serial matches
Init->>Auth: UPDATE is_active=1
else serial mismatch
Init-->>Handler: device_auth serial_number conflict
Handler-->>MQTT: device/{imei}/event_resp status=failed
end
Init->>Mapping: UPDATE status=activated, activated_at=now
Handler-->>MQTT: device/{imei}/event_resp with device_id and device_sn
MQTT-->>Device: Receive assigned identity
end
</pre>
<h3>Data Model</h3>
<pre class="mermaid diagram">
erDiagram
device_imei_mapping {
int id PK
varchar imei UK
varchar device_id UK
varchar serial_number
varchar status
datetime activated_at
datetime created_at
datetime updated_at
}
device_auth {
int id PK
varchar device_id UK
varchar serial_number
varchar batch_id
tinyint is_active
datetime created_at
datetime updated_at
}
device_imei_mapping ||--o| device_auth : activates
</pre>
<div class="source">Source: <code>talkingq-url/services/device_identity_initializer.py</code>, <code>talkingq-url/handlers/mqtt_handler.py</code></div>
</section>
<section id="binding">
<h2>03 Binding Flow</h2>
<p class="section-note">How a parent binds an authenticated device to a child through NFC confirmation.</p>
<h3>Device Binding</h3>
<pre class="mermaid diagram">
sequenceDiagram
autonumber
participant Parent as Parent in mini-program
participant Mini as banban-mini bind page
participant API as /banban/bind APIs
participant Binding as BindingService
participant DAO as BindingDAO
participant Auth as device_auth
participant MQTT as TalkingQMQTTService
participant Device as TalkingQ device
participant Card as cards
participant Bindings as device_bindings
participant History as device_bind_history
Parent->>Mini: Scan QR or open bind URL
Mini->>API: start_bind(device_id, serial_number, child_id)
API->>Binding: start_bind(user_id, device_id, serial_number, child_id)
Binding->>Auth: get_device_auth(device_id)
alt device missing, inactive, or serial mismatch
Binding-->>API: BindingError
API-->>Mini: 4xx error
else device is bindable
Binding->>DAO: ensure no active device binding
Binding->>DAO: create device_bind_session
Binding->>MQTT: send_bind_nfc_command(msg_id=006)
MQTT-->>Device: device/{device_id}/command
API-->>Mini: bind_token, expires_at
end
Device->>MQTT: NFC response / card UUID
MQTT->>Binding: finalize_nfc_bind(device_id, card_uuid)
Binding->>DAO: load latest pending bind session
Binding->>Card: activate_card(card_uuid, device_id)
Binding->>Bindings: create active binding
Binding->>History: write bind audit history
Binding->>DAO: mark session completed
</pre>
<h3>Related Tables</h3>
<pre class="mermaid diagram">
erDiagram
parents ||--o{ children : owns
parents ||--o{ device_bindings : owns
children ||--o{ device_bindings : current_child
device_auth ||--o{ device_bindings : device
device_auth ||--o{ device_bind_sessions : pending_bind
device_auth ||--o{ cards : card_owner_device
device_bindings ||--o{ device_bind_history : audit
</pre>
<div class="source">Source: <code>banban-mini/src/pages/bind/index.tsx</code>, <code>talkingq-url/banban/service/binding.py</code></div>
</section>
<section id="voice">
<h2>04 Voice And IM Flow</h2>
<p class="section-note">Parent voice upload, IM persistence, COS audio, and device delivery.</p>
<h3>Parent Voice Message To Device</h3>
<pre class="mermaid diagram">
sequenceDiagram
autonumber
participant Parent as Parent
participant Mini as Chat detail page
participant API as /banban chat APIs
participant Audio as Upload / audio handling
participant COS as Tencent COS
participant IM as IM service / DAO
participant DB as im_conversations + im_messages
participant Pending as device_pending_voice_messages
participant MQTT as MQTT broker
participant Device as TalkingQ device
Parent->>Mini: Long press "press to leave message"
Mini->>Mini: Taro.getRecorderManager records mp3/aac
Mini->>API: upload voice file + metadata
API->>Audio: validate and store audio
Audio->>COS: upload message audio
COS-->>Audio: media_file_key / public URL
API->>IM: create parent voice IM message
IM->>DB: upsert conversation and insert message
IM->>Pending: create pending voice delivery row
API-->>Mini: message item with media URL
Device->>MQTT: device/{device_id}/event msg_id=005 NFC listen
MQTT->>API: MQTT handler receives event
API->>Pending: find pending voice for card/device
API-->>MQTT: device/{device_id}/event_resp with audio URL(s)
MQTT-->>Device: Play pending audio
API->>Pending: mark delivered / increment delivery count
</pre>
<h3>Device / Child Voice Into IM</h3>
<pre class="mermaid diagram">
flowchart TD
Device["Device short press / voice upload\nmsg_id=011 or compatibility path"]
MQTT["TalkingQMQTTService"]
Upload["Audio upload / archive service"]
Resolve["Resolve bound owner and child\nfrom device_bindings"]
IM["IM service"]
Tables["im_conversations\nim_messages"]
Mini["banban-mini chat list/detail"]
Device --> MQTT --> Upload --> Resolve --> IM --> Tables
Tables -->|GET chat APIs| Mini
</pre>
<h3>Audio Domain</h3>
<pre class="mermaid diagram">
flowchart LR
Message["im_messages.media_file_key"]
Presenter["present_message_item / chat service"]
URL["mediaUrl returned to mini-program"]
Player["Taro InnerAudioContext"]
COS["COS downloadFile legal domain"]
Message --> Presenter --> URL --> Player
URL --> COS
</pre>
<div class="source">Source: <code>banban-mini/src/pages/chat/detail/index.tsx</code>, <code>talkingq-url/banban/service/im.py</code></div>
</section>
<section id="location">
<h2>05 Location Flow</h2>
<p class="section-note">Device-sourced current location and trajectory shown in the mini-program.</p>
<h3>Device-Sourced Location</h3>
<pre class="mermaid diagram">
sequenceDiagram
autonumber
participant Device as TalkingQ device
participant MQTT as MQTT broker
participant Handler as TalkingQMQTTService
participant Location as Location service
participant Current as child_location_current
participant History as child_location_history
participant Mini as banban-mini location page
participant API as /banban/devices/{device_id}/location
Device->>MQTT: msg_id=000 with latitude/longitude or GPS response
MQTT->>Handler: deliver device event/response
Handler->>Location: report_mqtt_device_location(...)
Location->>Location: resolve active child binding
Location->>Current: upsert latest location
Location->>History: insert location history point
Handler-->>MQTT: success event_resp
Mini->>API: GET current location
API->>Current: read current row for bound child/device
API-->>Mini: current location, accuracy, battery, address fields
Mini->>API: GET trajectory
API->>History: read recent/today points
API-->>Mini: trajectory points
</pre>
<h3>Data Model</h3>
<pre class="mermaid diagram">
erDiagram
device_auth ||--o{ child_location_current : device
children ||--o{ child_location_current : child
device_auth ||--o{ child_location_history : device
children ||--o{ child_location_history : child
device_bindings ||--o{ child_location_current : active_binding_source
</pre>
<div class="source">Source: <code>banban-mini/src/pages/location/index.tsx</code>, <code>talkingq-url/banban/service/location.py</code></div>
</section>
<section id="alarm">
<h2>06 Alarm And Notification Flow</h2>
<p class="section-note">Device alarm persistence and user-visible notifications.</p>
<h3>Device Alarm To User Notification</h3>
<pre class="mermaid diagram">
sequenceDiagram
autonumber
participant Device as TalkingQ device
participant MQTT as MQTT broker
participant Handler as TalkingQMQTTService
participant Alarm as Alarm service
participant Location as Location snapshot
participant DB as device_alarm_events
participant Banner as system banner polling
participant Mini as banban-mini
participant WxMP as WeChat Official Account
participant SMS as SMS provider
Device->>MQTT: alarm event msg_id=010
MQTT->>Handler: deliver alarm payload
Handler->>Alarm: create alarm event
Alarm->>Location: attach current location snapshot if available
Alarm->>DB: insert device_alarm_events
Alarm->>WxMP: schedule/send template notification if bound
Alarm->>SMS: send SMS if phone notification is enabled
Banner->>DB: poll latest/new alarm
Mini->>Banner: show banner / alarm history on device home
Handler-->>MQTT: alarm event_resp
</pre>
<h3>Supporting Notification Bind</h3>
<pre class="mermaid diagram">
flowchart TD
Mini["banban-mini management page"]
Phone["WeChat phone authorization"]
WxBind["WeChat MP bind start/status"]
Backend["talkingq-url wechat-mp routers/services"]
Parent["parents / parent_wechat_* tables"]
Notify["wechat_mp_notification_service"]
Mini --> Phone --> Backend --> Parent
Mini --> WxBind --> Backend --> Parent
Parent --> Notify
</pre>
<h3>Key Tables</h3>
<pre class="mermaid diagram">
erDiagram
device_auth ||--o{ device_alarm_events : reports
children ||--o{ device_alarm_events : alarm_owner
parents ||--o{ parent_wechat_accounts : mp_account
parents ||--o{ parent_wechat_identities : wx_identity
parents ||--o{ wechat_mp_bind_states : bind_state
</pre>
<div class="source">Source: <code>banban-mini/src/services/system-banner.ts</code>, <code>talkingq-url/banban/service/wechat_mp_notification.py</code></div>
</section>
<section id="deployment">
<h2>07 Deployment</h2>
<p class="section-note">Public API host, reverse proxy, backend process, database, MQTT, COS, and certificate renewal.</p>
<h3>Public Deployment Shape</h3>
<pre class="mermaid diagram">
flowchart TD
User["WeChat mini-program user"]
Domain["banban.api.talkingq.com"]
Nginx["Nginx container\nTLS + reverse proxy + static verify files"]
FastAPI["talkingq-url FastAPI process\n/home/ubuntu/banban-backend"]
MySQL["1Panel MySQL container\nMySQL 8.x"]
MQTT["Configured MQTT broker"]
COS["Tencent COS"]
Certbot["certbot container\nLet's Encrypt renewals"]
User -->|HTTPS| Domain --> Nginx --> FastAPI
FastAPI --> MySQL
FastAPI --> MQTT
FastAPI --> COS
Certbot --> Nginx
</pre>
<h3>Startup Responsibilities</h3>
<pre class="mermaid diagram">
sequenceDiagram
autonumber
participant Proc as FastAPI process
participant Init as init_db
participant Scheduler as TaskScheduler
participant MQTT as TalkingQMQTTService
participant Roles as role_manager
participant Firmware as firmware_scanner
Proc->>Init: initialize and migrate database tables
Proc->>Scheduler: start periodic tasks
Proc->>MQTT: connect to configured broker
Proc->>Roles: initialize role definitions
Proc->>Firmware: scan firmware files
Proc-->>Proc: serve API routes and assets
</pre>
<div class="source">Source: <code>talkingq-url/docker-compose.yml</code>, <code>talkingq-url/nginx.conf</code>, <code>talkingq-url/main.py</code></div>
</section>
<p class="fallback">
If diagrams do not render, open this file with network access enabled or
render the Markdown files in a Mermaid-capable viewer such as GitHub,
GitLab, VS Code with Mermaid support, or Mermaid Live Editor.
</p>
</main>
<script type="module">
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs";
mermaid.initialize({
startOnLoad: true,
securityLevel: "loose",
theme: "default",
flowchart: {
htmlLabels: true,
curve: "basis"
},
sequence: {
mirrorActors: false
}
});
</script>
</body>
</html>