601 lines
19 KiB
HTML
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>
|