Initial commit: Happy Up monorepo through Sprint 5.
Document-driven MVP with FastAPI backend, Vue H5, WeChat mini shell, product demo, and Docker dev stack. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
# API & Database Contracts
|
||||
|
||||
本目录为工程化使用的**契约副本**,单一事实来源仍为文档包:
|
||||
|
||||
| 文件 | 文档源路径 |
|
||||
|------|-----------|
|
||||
| `openapi.yaml` | `doc/Kids_AI_Posture_Platform_BUSINESS_DELIVERY_V1.0/07_API接口/openapi.yaml` |
|
||||
| `database.sql` | `doc/Kids_AI_Posture_Platform_BUSINESS_DELIVERY_V1.0/06_数据库设计/database.sql` |
|
||||
|
||||
更新契约时请先改文档包,再执行:
|
||||
|
||||
```bash
|
||||
make sync-contracts
|
||||
```
|
||||
@@ -0,0 +1,316 @@
|
||||
-- Kids AI Posture Platform V1.0 database schema
|
||||
-- MySQL 8.x
|
||||
-- 目标:满足审计、幂等、治理、隐私与可追溯性要求
|
||||
|
||||
CREATE TABLE tenants (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
type VARCHAR(32) NOT NULL DEFAULT 'organization',
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'active',
|
||||
retention_days INT NOT NULL DEFAULT 1095,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
CHECK (type IN ('organization','school','clinic','mall','other')),
|
||||
CHECK (status IN ('active','inactive','frozen','closed')),
|
||||
CHECK (retention_days BETWEEN 30 AND 3650)
|
||||
);
|
||||
|
||||
CREATE TABLE users (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
tenant_id BIGINT NULL,
|
||||
phone VARCHAR(32),
|
||||
phone_hash VARCHAR(128),
|
||||
wechat_openid VARCHAR(128),
|
||||
role VARCHAR(32) NOT NULL DEFAULT 'parent',
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'active',
|
||||
consent_signed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
privacy_version VARCHAR(16) DEFAULT 'v1',
|
||||
last_login_at TIMESTAMP NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
KEY idx_users_tenant_role (tenant_id, role),
|
||||
UNIQUE KEY uk_users_phone_hash (phone_hash),
|
||||
UNIQUE KEY uk_users_wechat_openid (wechat_openid),
|
||||
CHECK (role IN ('parent','coach','org_admin','platform_admin')),
|
||||
CHECK (status IN ('active','inactive','blocked'))
|
||||
);
|
||||
|
||||
CREATE TABLE users_passwords (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id BIGINT NOT NULL,
|
||||
password_hash VARCHAR(256) NOT NULL,
|
||||
salt VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
KEY idx_user_password (user_id)
|
||||
);
|
||||
|
||||
CREATE TABLE children (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
tenant_id BIGINT NULL,
|
||||
parent_user_id BIGINT NOT NULL,
|
||||
coach_user_id BIGINT NULL,
|
||||
name VARCHAR(64) NOT NULL,
|
||||
gender VARCHAR(16) NOT NULL DEFAULT 'unknown',
|
||||
birthday DATE NOT NULL,
|
||||
height DECIMAL(5,2),
|
||||
weight DECIMAL(5,2),
|
||||
contraindications TEXT,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'active',
|
||||
deleted_at TIMESTAMP NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
KEY idx_children_parent (parent_user_id),
|
||||
KEY idx_children_tenant_coach (tenant_id, coach_user_id),
|
||||
KEY idx_children_status (status),
|
||||
CHECK (gender IN ('male','female','unknown')),
|
||||
CHECK (status IN ('active','inactive','archived')),
|
||||
CHECK (height IS NULL OR (height > 20 AND height < 240)),
|
||||
CHECK (weight IS NULL OR (weight > 5 AND weight < 250))
|
||||
);
|
||||
|
||||
CREATE TABLE child_measurements (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
child_id BIGINT NOT NULL,
|
||||
height DECIMAL(5,2),
|
||||
weight DECIMAL(5,2),
|
||||
source VARCHAR(32) NOT NULL DEFAULT 'manual',
|
||||
measured_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_measurements_child_time (child_id, measured_at),
|
||||
CHECK (source IN ('manual','device','coach','report')),
|
||||
CHECK (height IS NULL OR (height > 20 AND height < 240)),
|
||||
CHECK (weight IS NULL OR (weight > 5 AND weight < 250))
|
||||
);
|
||||
|
||||
CREATE TABLE videos (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
child_id BIGINT NOT NULL,
|
||||
uploaded_by BIGINT NOT NULL,
|
||||
scene VARCHAR(64) NOT NULL,
|
||||
object_key VARCHAR(512) NOT NULL,
|
||||
duration_seconds INT,
|
||||
width INT,
|
||||
height INT,
|
||||
size_bytes BIGINT,
|
||||
codec VARCHAR(32),
|
||||
quality_result JSON,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'uploaded',
|
||||
archive_until_date DATE NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
KEY idx_videos_child_scene (child_id, scene),
|
||||
KEY idx_videos_status (status),
|
||||
UNIQUE KEY uk_videos_object_key (object_key),
|
||||
CHECK (scene IN ('front_posture','side_posture','squat','balance','gait','custom')),
|
||||
CHECK (status IN ('uploaded','transcoding','failed','ready','archived')),
|
||||
CHECK (duration_seconds IS NULL OR duration_seconds BETWEEN 1 AND 1800),
|
||||
CHECK (size_bytes IS NULL OR size_bytes BETWEEN 1 AND 10737418240)
|
||||
);
|
||||
|
||||
CREATE TABLE analysis_tasks (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
child_id BIGINT NOT NULL,
|
||||
video_id BIGINT NOT NULL,
|
||||
task_type VARCHAR(64) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'CREATED',
|
||||
progress INT NOT NULL DEFAULT 0,
|
||||
model_version VARCHAR(64),
|
||||
error_code VARCHAR(64),
|
||||
error_message VARCHAR(512),
|
||||
result JSON,
|
||||
idempotency_key VARCHAR(128),
|
||||
retry_count INT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
started_at TIMESTAMP NULL,
|
||||
finished_at TIMESTAMP NULL,
|
||||
UNIQUE KEY uk_analysis_idempotency (child_id, video_id, task_type, idempotency_key),
|
||||
KEY idx_analysis_child_status (child_id, status),
|
||||
KEY idx_analysis_video (video_id),
|
||||
KEY idx_analysis_created (created_at),
|
||||
CHECK (status IN ('CREATED','QUEUED','PROCESSING','SUCCEEDED','FAILED','CANCELLED')),
|
||||
CHECK (task_type IN ('posture_screening','movement_scoring','reassessment')),
|
||||
CHECK (progress BETWEEN 0 AND 100),
|
||||
CHECK (retry_count BETWEEN 0 AND 20)
|
||||
);
|
||||
|
||||
CREATE TABLE reports (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
child_id BIGINT NOT NULL,
|
||||
task_id BIGINT NOT NULL,
|
||||
report_type VARCHAR(64) NOT NULL DEFAULT 'posture_screening',
|
||||
risk_level VARCHAR(32) NOT NULL,
|
||||
summary TEXT NOT NULL,
|
||||
metrics JSON NOT NULL,
|
||||
recommendations JSON NOT NULL,
|
||||
disclaimer TEXT NOT NULL,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'draft',
|
||||
reviewed_by BIGINT NULL,
|
||||
reviewed_at TIMESTAMP NULL,
|
||||
published_at TIMESTAMP NULL,
|
||||
expires_at DATE NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_reports_task (task_id),
|
||||
KEY idx_reports_child_status (child_id, status),
|
||||
CHECK (risk_level IN ('low','medium','high','review_required')),
|
||||
CHECK (status IN ('draft','published','retracted','archived'))
|
||||
);
|
||||
|
||||
CREATE TABLE exercises (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
category VARCHAR(64) NOT NULL,
|
||||
target_issue VARCHAR(128),
|
||||
difficulty VARCHAR(32) NOT NULL DEFAULT 'basic',
|
||||
duration_seconds INT NOT NULL DEFAULT 60,
|
||||
media_url VARCHAR(512),
|
||||
thumbnail_url VARCHAR(512),
|
||||
rules JSON,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'active',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
KEY idx_exercises_category (category, status),
|
||||
CHECK (difficulty IN ('basic','intermediate','advanced')),
|
||||
CHECK (status IN ('active','offline','deprecated')),
|
||||
CHECK (duration_seconds BETWEEN 20 AND 1800)
|
||||
);
|
||||
|
||||
CREATE TABLE training_plans (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
child_id BIGINT NOT NULL,
|
||||
report_id BIGINT NULL,
|
||||
coach_user_id BIGINT NULL,
|
||||
goal VARCHAR(256) NOT NULL,
|
||||
cycle_days INT NOT NULL DEFAULT 28,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'draft',
|
||||
plan_detail JSON NOT NULL,
|
||||
started_at TIMESTAMP NULL,
|
||||
ended_at TIMESTAMP NULL,
|
||||
deleted_at TIMESTAMP NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
KEY idx_training_plans_child_status (child_id, status),
|
||||
CHECK (cycle_days BETWEEN 7 AND 180),
|
||||
CHECK (status IN ('draft','active','completed','paused','cancelled'))
|
||||
);
|
||||
|
||||
CREATE TABLE training_records (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
plan_id BIGINT NOT NULL,
|
||||
child_id BIGINT NOT NULL,
|
||||
exercise_id BIGINT NOT NULL,
|
||||
completed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
score INT,
|
||||
duration_seconds INT,
|
||||
feedback JSON,
|
||||
note VARCHAR(512),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
KEY idx_training_records_plan (plan_id),
|
||||
KEY idx_training_records_child_time (child_id, created_at),
|
||||
CHECK (score IS NULL OR score BETWEEN 0 AND 100),
|
||||
CHECK (duration_seconds IS NULL OR duration_seconds BETWEEN 1 AND 7200)
|
||||
);
|
||||
|
||||
CREATE TABLE products (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
tenant_id BIGINT NULL,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
product_type VARCHAR(32) NOT NULL,
|
||||
price_cents INT NOT NULL,
|
||||
duration_days INT NULL,
|
||||
benefits JSON NOT NULL,
|
||||
limit_child INTEGER NOT NULL DEFAULT 1,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'active',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CHECK (product_type IN ('video_pack','training_plan','membership','assessment_bundle','course_package')),
|
||||
CHECK (status IN ('active','inactive','discontinued')),
|
||||
CHECK (price_cents >= 0),
|
||||
CHECK (duration_days IS NULL OR duration_days BETWEEN 1 AND 3650)
|
||||
);
|
||||
|
||||
CREATE TABLE orders (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id BIGINT NOT NULL,
|
||||
child_id BIGINT NULL,
|
||||
product_id BIGINT NOT NULL,
|
||||
amount_cents INT NOT NULL,
|
||||
pay_status VARCHAR(32) NOT NULL DEFAULT 'pending',
|
||||
payment_channel VARCHAR(32),
|
||||
paid_at TIMESTAMP NULL,
|
||||
canceled_at TIMESTAMP NULL,
|
||||
refund_status VARCHAR(32) DEFAULT 'none',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
KEY idx_orders_user_status (user_id, pay_status),
|
||||
KEY idx_orders_child (child_id),
|
||||
CHECK (pay_status IN ('pending','paid','failed','closed','refunding','refunded')),
|
||||
CHECK (refund_status IN ('none','requested','processing','completed','rejected')),
|
||||
CHECK (amount_cents >= 0)
|
||||
);
|
||||
|
||||
CREATE TABLE operation_leads (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
tenant_id BIGINT NOT NULL,
|
||||
child_id BIGINT NULL,
|
||||
source VARCHAR(64) NOT NULL,
|
||||
stage VARCHAR(32) NOT NULL DEFAULT 'new',
|
||||
owner_user_id BIGINT NULL,
|
||||
score INT DEFAULT 0,
|
||||
priority TINYINT DEFAULT 3,
|
||||
next_follow_at TIMESTAMP NULL,
|
||||
note TEXT,
|
||||
touched_at TIMESTAMP NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
KEY idx_leads_tenant_stage (tenant_id, stage),
|
||||
KEY idx_leads_owner_next (owner_user_id, next_follow_at),
|
||||
CHECK (source IN ('wechat','h5','miniapp','partner','referral','offline_campaign')),
|
||||
CHECK (stage IN ('new','contacted','qualified','closed','lost','churned')),
|
||||
CHECK (priority BETWEEN 1 AND 5),
|
||||
CHECK (score BETWEEN 0 AND 100)
|
||||
);
|
||||
|
||||
CREATE TABLE audit_logs (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
tenant_id BIGINT NULL,
|
||||
actor_user_id BIGINT NOT NULL,
|
||||
action VARCHAR(128) NOT NULL,
|
||||
resource_type VARCHAR(64) NOT NULL,
|
||||
resource_id BIGINT NULL,
|
||||
request_id VARCHAR(64),
|
||||
ip VARCHAR(64),
|
||||
user_agent VARCHAR(256),
|
||||
payload JSON,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
KEY idx_audit_actor_time (actor_user_id, created_at),
|
||||
KEY idx_audit_resource (resource_type, resource_id)
|
||||
);
|
||||
|
||||
CREATE TABLE data_retention_rules (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
tenant_id BIGINT NULL,
|
||||
resource_type VARCHAR(64) NOT NULL,
|
||||
archive_after_days INT NOT NULL,
|
||||
delete_after_days INT NOT NULL,
|
||||
legal_basis VARCHAR(128),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_retention (tenant_id, resource_type),
|
||||
CHECK (delete_after_days >= archive_after_days),
|
||||
CHECK (archive_after_days >= 30)
|
||||
);
|
||||
|
||||
CREATE TABLE api_idempotency_keys (
|
||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||
scope VARCHAR(32) NOT NULL,
|
||||
idempotency_key VARCHAR(128) NOT NULL,
|
||||
request_path VARCHAR(255) NOT NULL,
|
||||
request_hash CHAR(64) NOT NULL,
|
||||
response_status INT,
|
||||
response_body JSON,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uk_idempotency_scope_key (scope, idempotency_key),
|
||||
KEY idx_idempotency_created (created_at)
|
||||
);
|
||||
|
||||
-- 运行时可选索引补充说明(按需执行)
|
||||
-- CREATE INDEX idx_analysis_status_updated ON analysis_tasks(status, updated_at);
|
||||
-- CREATE INDEX idx_reports_risk_level ON reports(risk_level, created_at);
|
||||
-- CREATE INDEX idx_orders_pay_status_updated ON orders(pay_status, updated_at);
|
||||
@@ -0,0 +1,670 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: Kids AI Posture Platform API
|
||||
version: 1.0.0
|
||||
description: |
|
||||
儿童AI体态管理与运动康复平台 V1.0 API 契约。
|
||||
所有报告用于健康管理建议,不构成医疗诊断。接口返回统一包含 requestId 以便追踪。
|
||||
servers:
|
||||
- url: https://api.example.com
|
||||
description: production
|
||||
- url: https://staging-api.example.com
|
||||
description: staging
|
||||
security:
|
||||
- bearerAuth: []
|
||||
|
||||
x-error-model: &error-model
|
||||
code: integer
|
||||
message: string
|
||||
details:
|
||||
type: object
|
||||
additionalProperties: true
|
||||
requestId: string
|
||||
|
||||
tags:
|
||||
- name: Auth
|
||||
- name: Children
|
||||
- name: Videos
|
||||
- name: Analysis
|
||||
- name: Reports
|
||||
- name: Training
|
||||
- name: Admin
|
||||
|
||||
paths:
|
||||
/api/auth/login:
|
||||
post:
|
||||
tags: [Auth]
|
||||
summary: 手机号或微信授权登录
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/LoginRequest'
|
||||
examples:
|
||||
phone:
|
||||
summary: 手机验证码
|
||||
value:
|
||||
loginType: phone_code
|
||||
credential: "186xxxx0000"
|
||||
code: "682139"
|
||||
wechat:
|
||||
summary: 微信授权码
|
||||
value:
|
||||
loginType: wechat
|
||||
credential: "wx_auth_code"
|
||||
responses:
|
||||
'200':
|
||||
description: 登录成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/LoginResponse'
|
||||
'401':
|
||||
description: 登录失败
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
|
||||
/api/children:
|
||||
get:
|
||||
tags: [Children]
|
||||
summary: 获取当前用户可访问的儿童档案
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
schema: { type: integer, minimum: 1, default: 1 }
|
||||
- name: pageSize
|
||||
in: query
|
||||
schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
|
||||
responses:
|
||||
'200':
|
||||
description: 儿童档案分页列表
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChildrenPagedResponse'
|
||||
post:
|
||||
tags: [Children]
|
||||
summary: 创建儿童档案
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChildCreateRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: 创建成功
|
||||
headers:
|
||||
Idempotency-Key:
|
||||
description: 冪等键回显,用于重试去重核对
|
||||
schema: { type: string }
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChildResponse'
|
||||
'400':
|
||||
description: 请求参数错误
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
|
||||
/api/children/{childId}:
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/ChildId'
|
||||
get:
|
||||
tags: [Children]
|
||||
summary: 获取儿童档案详情
|
||||
responses:
|
||||
'200':
|
||||
description: 档案详情
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChildResponse'
|
||||
'404':
|
||||
description: 资源不存在
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
patch:
|
||||
tags: [Children]
|
||||
summary: 更新儿童档案
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChildUpdateRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 更新成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChildResponse'
|
||||
'409':
|
||||
description: 更新冲突(数据并发)
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
delete:
|
||||
tags: [Children]
|
||||
summary: 注销/归档儿童档案
|
||||
responses:
|
||||
'204':
|
||||
description: 归档成功
|
||||
|
||||
/api/videos/upload-token:
|
||||
post:
|
||||
tags: [Videos]
|
||||
summary: 获取视频直传凭证
|
||||
description: 建议请求头携带 Idempotency-Key,避免重试产生重复录像对象。
|
||||
parameters:
|
||||
- name: Idempotency-Key
|
||||
in: header
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UploadTokenRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: 上传凭证
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UploadTokenResponse'
|
||||
'429':
|
||||
description: 请求过于频繁
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
|
||||
/api/videos:
|
||||
post:
|
||||
tags: [Videos]
|
||||
summary: 登记已上传视频
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/VideoCreateRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: 视频登记成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/VideoResponse'
|
||||
'409':
|
||||
description: 去重/重复提交
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
|
||||
/api/analysis/tasks:
|
||||
post:
|
||||
tags: [Analysis]
|
||||
summary: 创建AI分析任务
|
||||
description: 同一 childId+videoId+taskType 可复用幂等键创建。
|
||||
parameters:
|
||||
- name: Idempotency-Key
|
||||
in: header
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AnalysisTaskCreateRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: 任务创建成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AnalysisTaskResponse'
|
||||
'409':
|
||||
description: 重复任务,返回已有任务
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AnalysisTaskResponse'
|
||||
|
||||
/api/analysis/tasks/{taskId}:
|
||||
get:
|
||||
tags: [Analysis]
|
||||
summary: 查询分析任务状态
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/TaskId'
|
||||
responses:
|
||||
'200':
|
||||
description: 任务状态
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AnalysisTaskResponse'
|
||||
'404':
|
||||
description: 任务不存在
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
|
||||
/api/analysis/tasks/{taskId}/cancel:
|
||||
post:
|
||||
tags: [Analysis]
|
||||
summary: 取消分析任务
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/TaskId'
|
||||
responses:
|
||||
'200':
|
||||
description: 取消成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/StandardResponse'
|
||||
|
||||
/api/analysis/webhook:
|
||||
post:
|
||||
tags: [Analysis]
|
||||
summary: 分析结果回调(异步)
|
||||
description: 系统内部回调给后端事件网关,需使用 HMAC 签名。
|
||||
security: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AnalysisTaskWebhook'
|
||||
responses:
|
||||
'204':
|
||||
description: 接收成功
|
||||
|
||||
/api/reports/{reportId}:
|
||||
get:
|
||||
tags: [Reports]
|
||||
summary: 获取体态分析报告
|
||||
parameters:
|
||||
- $ref: '#/components/parameters/ReportId'
|
||||
responses:
|
||||
'200':
|
||||
description: 报告详情
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ReportResponse'
|
||||
|
||||
/api/training/plans:
|
||||
post:
|
||||
tags: [Training]
|
||||
summary: 创建训练计划
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TrainingPlanCreateRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: 训练计划创建成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TrainingPlanResponse'
|
||||
|
||||
/api/training/plans/{planId}:
|
||||
parameters:
|
||||
- name: planId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
get:
|
||||
tags: [Training]
|
||||
summary: 获取训练计划
|
||||
responses:
|
||||
'200':
|
||||
description: 训练计划详情
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TrainingPlanResponse'
|
||||
|
||||
/api/training/plans/{planId}/records:
|
||||
post:
|
||||
tags: [Training]
|
||||
summary: 提交训练打卡记录
|
||||
parameters:
|
||||
- name: planId
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: integer }
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TrainingRecordCreateRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: 打卡成功
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/TrainingRecordResponse'
|
||||
|
||||
/api/admin/dashboard:
|
||||
get:
|
||||
tags: [Admin]
|
||||
summary: 获取机构运营看板
|
||||
responses:
|
||||
'200':
|
||||
description: 看板指标
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AdminDashboardResponse'
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
parameters:
|
||||
ChildId:
|
||||
name: childId
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: integer }
|
||||
TaskId:
|
||||
name: taskId
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: integer }
|
||||
ReportId:
|
||||
name: reportId
|
||||
in: path
|
||||
required: true
|
||||
schema: { type: integer }
|
||||
schemas:
|
||||
ApiMeta:
|
||||
type: object
|
||||
properties:
|
||||
requestId: { type: string }
|
||||
timestamp: { type: string, format: date-time }
|
||||
traceId: { type: string }
|
||||
StandardResponse:
|
||||
type: object
|
||||
properties:
|
||||
code: { type: integer }
|
||||
message: { type: string }
|
||||
data: { type: object, nullable: true }
|
||||
meta: { $ref: '#/components/schemas/ApiMeta' }
|
||||
ErrorResponse:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/StandardResponse'
|
||||
- type: object
|
||||
properties:
|
||||
code: { type: integer, example: 10003 }
|
||||
message: { type: string, example: parameter_validation_failed }
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
errorCode: { type: string }
|
||||
path: { type: string }
|
||||
PagedMeta:
|
||||
type: object
|
||||
properties:
|
||||
page: { type: integer }
|
||||
pageSize: { type: integer }
|
||||
total: { type: integer }
|
||||
hasMore: { type: boolean }
|
||||
LoginRequest:
|
||||
type: object
|
||||
required: [loginType, credential]
|
||||
properties:
|
||||
loginType: { type: string, enum: [phone_code, wechat] }
|
||||
credential: { type: string }
|
||||
code: { type: string }
|
||||
LoginResponse:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/StandardResponse'
|
||||
- type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
token: { type: string }
|
||||
user: { $ref: '#/components/schemas/User' }
|
||||
User:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: integer }
|
||||
phoneMasked: { type: string }
|
||||
role: { type: string, enum: [parent, coach, org_admin, platform_admin] }
|
||||
ChildCreateRequest:
|
||||
type: object
|
||||
required: [name, birthday]
|
||||
properties:
|
||||
name: { type: string }
|
||||
birthday: { type: string, format: date }
|
||||
gender: { type: string, enum: [male, female, unknown] }
|
||||
height: { type: number }
|
||||
weight: { type: number }
|
||||
contraindications: { type: string }
|
||||
ChildUpdateRequest:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/ChildCreateRequest'
|
||||
Child:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: integer }
|
||||
name: { type: string }
|
||||
birthday: { type: string, format: date }
|
||||
age: { type: integer }
|
||||
height: { type: number }
|
||||
weight: { type: number }
|
||||
status: { type: string }
|
||||
ChildResponse:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/StandardResponse'
|
||||
- type: object
|
||||
properties:
|
||||
data: { $ref: '#/components/schemas/Child' }
|
||||
ChildrenPagedResponse:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/StandardResponse'
|
||||
- type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
list:
|
||||
type: array
|
||||
items: { $ref: '#/components/schemas/Child' }
|
||||
page: { $ref: '#/components/schemas/PagedMeta' }
|
||||
UploadTokenRequest:
|
||||
type: object
|
||||
required: [childId, fileName, contentType, size]
|
||||
properties:
|
||||
childId: { type: integer }
|
||||
fileName: { type: string }
|
||||
contentType: { type: string, example: video/mp4 }
|
||||
size: { type: integer }
|
||||
UploadTokenResponse:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/StandardResponse'
|
||||
- type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
uploadUrl: { type: string }
|
||||
objectKey: { type: string }
|
||||
expireAt: { type: string, format: date-time }
|
||||
VideoCreateRequest:
|
||||
type: object
|
||||
required: [childId, objectKey, scene]
|
||||
properties:
|
||||
childId: { type: integer }
|
||||
objectKey: { type: string }
|
||||
scene:
|
||||
type: string
|
||||
enum: [front_posture, side_posture, squat, balance, gait]
|
||||
captureHint: { type: string }
|
||||
VideoResponse:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/StandardResponse'
|
||||
- type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: integer }
|
||||
status: { type: string, enum: [uploaded, rejected, archived] }
|
||||
AnalysisTaskCreateRequest:
|
||||
type: object
|
||||
required: [childId, videoId, taskType]
|
||||
properties:
|
||||
childId: { type: integer }
|
||||
videoId: { type: integer }
|
||||
taskType: { type: string, enum: [posture_screening, movement_scoring, reassessment] }
|
||||
AnalysisTaskResponse:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/StandardResponse'
|
||||
- type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: integer }
|
||||
status:
|
||||
type: string
|
||||
enum: [CREATED, QUEUED, PROCESSING, SUCCEEDED, FAILED, CANCELLED]
|
||||
progress: { type: integer, minimum: 0, maximum: 100 }
|
||||
errorCode: { type: string }
|
||||
reportId: { type: integer }
|
||||
retryCount: { type: integer }
|
||||
AnalysisTaskWebhook:
|
||||
type: object
|
||||
required: [taskId, status]
|
||||
properties:
|
||||
taskId: { type: integer }
|
||||
status:
|
||||
type: string
|
||||
enum: [SUCCEEDED, FAILED, CANCELLED]
|
||||
progress: { type: integer }
|
||||
errorCode: { type: string }
|
||||
result: { type: object }
|
||||
signature: { type: string }
|
||||
ReportResponse:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/StandardResponse'
|
||||
- type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: integer }
|
||||
childId: { type: integer }
|
||||
taskId: { type: integer }
|
||||
riskLevel: { type: string, enum: [low, medium, high, review_required] }
|
||||
summary: { type: string }
|
||||
metrics:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name: { type: string }
|
||||
value: { type: number }
|
||||
level: { type: string }
|
||||
confidence: { type: number }
|
||||
recommendations:
|
||||
type: array
|
||||
items: { type: string }
|
||||
disclaimer: { type: string }
|
||||
reviewedBy: { type: integer, nullable: true }
|
||||
TrainingPlanCreateRequest:
|
||||
type: object
|
||||
required: [childId, goal, cycleDays]
|
||||
properties:
|
||||
childId: { type: integer }
|
||||
reportId: { type: integer }
|
||||
goal: { type: string }
|
||||
cycleDays: { type: integer, minimum: 7 }
|
||||
exerciseIds:
|
||||
type: array
|
||||
items: { type: integer }
|
||||
constraints:
|
||||
type: object
|
||||
properties:
|
||||
maxDailyMinutes: { type: integer }
|
||||
coachNotes: { type: string }
|
||||
TrainingPlanResponse:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/StandardResponse'
|
||||
- type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: integer }
|
||||
status: { type: string, enum: [draft, active, completed, paused] }
|
||||
detail: { type: object }
|
||||
startedAt: { type: string, format: date-time }
|
||||
endedAt: { type: string, format: date-time, nullable: true }
|
||||
TrainingRecordCreateRequest:
|
||||
type: object
|
||||
required: [exerciseId, completed]
|
||||
properties:
|
||||
exerciseId: { type: integer }
|
||||
completed: { type: boolean }
|
||||
score: { type: integer, minimum: 0, maximum: 100 }
|
||||
durationSeconds: { type: integer }
|
||||
note: { type: string }
|
||||
media:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
TrainingRecordResponse:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/StandardResponse'
|
||||
- type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
id: { type: integer }
|
||||
planId: { type: integer }
|
||||
createdAt: { type: string, format: date-time }
|
||||
AdminDashboardResponse:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/StandardResponse'
|
||||
- type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
properties:
|
||||
newChildren: { type: integer }
|
||||
uploadedVideos: { type: integer }
|
||||
completedReports: { type: integer }
|
||||
activePlans: { type: integer }
|
||||
conversionRate: { type: number }
|
||||
reassessmentCompletionRate: { type: number }
|
||||
Reference in New Issue
Block a user