新增小程序后端代码,包括数据库、路由、服务层等。

小程序前后端打通,包括登录、注册、绑定设备、查询绑定信息,修改小朋友名称等功能。
This commit is contained in:
HycJack
2026-04-13 01:54:57 +08:00
parent 33715373b0
commit a22fcafea9
46 changed files with 3713 additions and 268 deletions

View File

@@ -0,0 +1,226 @@
import logging
import uuid
from collections.abc import Mapping
from datetime import datetime, timedelta
from typing import Optional
from sqlalchemy import text
from app.dao import BaseDAO
logger = logging.getLogger("app.dao.binding")
class BindingDAO(BaseDAO):
def start_bind(self, user_id: int, device_id: str, child_id: int) -> str:
bind_token = str(uuid.uuid4())
expires_at = datetime.utcnow() + timedelta(minutes=10)
self.db.execute(
text(
"""
INSERT INTO device_bind_sessions (bind_token, device_id, initiator_user_id, target_child_id, expires_at, status)
VALUES (:bind_token, :device_id, :initiator_user_id, :target_child_id, :expires_at, 1)
"""
),
{
"bind_token": bind_token,
"device_id": device_id,
"initiator_user_id": user_id,
"target_child_id": child_id,
"expires_at": expires_at,
},
)
self.commit()
return bind_token
def get_session(self, bind_token: str, user_id: int) -> Optional[Mapping]:
return (
self.db.execute(
text(
"SELECT * FROM device_bind_sessions WHERE bind_token = :bind_token AND initiator_user_id = :user_id"
),
{"bind_token": bind_token, "user_id": user_id},
)
.mappings()
.first()
)
def confirm_bind(self, session_id: int, device_id: str, child_id: int, user_id: int) -> None:
self.db.execute(
text("UPDATE device_bind_sessions SET status = 2, confirmed_at = CURRENT_TIMESTAMP WHERE id = :id"),
{"id": session_id},
)
existing = self.db.execute(
text("SELECT id FROM device_bindings WHERE device_id = :device_id OR child_id = :child_id"),
{"device_id": device_id, "child_id": child_id},
).scalar_one_or_none()
if existing:
self.db.execute(
text(
"UPDATE device_bindings SET child_id = :child_id, status = 1, unbound_at = NULL WHERE device_id = :device_id"
),
{"device_id": device_id, "child_id": child_id},
)
else:
self.db.execute(
text(
"INSERT INTO device_bindings (device_id, child_id, status, bound_at) VALUES (:device_id, :child_id, 1, CURRENT_TIMESTAMP)"
),
{"device_id": device_id, "child_id": child_id},
)
self.db.execute(
text(
"INSERT INTO device_bind_history (device_id, child_id, bound_by_user_id, bind_source, bound_at) VALUES (:device_id, :child_id, :user_id, 1, CURRENT_TIMESTAMP)"
),
{"device_id": device_id, "child_id": child_id, "user_id": user_id},
)
self.commit()
def direct_bind(self, device_id: str, child_id: int, user_id: int) -> None:
# Check if binding exists for this device_id
existing_by_device = (
self.db.execute(
text("SELECT id FROM device_bindings WHERE device_id = :device_id"),
{"device_id": device_id},
).fetchone()
)
# Check if this child is already bound to a different device
existing_by_child = (
self.db.execute(
text("SELECT id FROM device_bindings WHERE child_id = :child_id AND status = 1"),
{"child_id": child_id},
).fetchone()
)
if existing_by_device:
# Update existing binding for this device
self.db.execute(
text(
"UPDATE device_bindings SET child_id = :child_id, status = 1, unbound_at = NULL WHERE device_id = :device_id"
),
{"device_id": device_id, "child_id": child_id},
)
self.db.execute(
text(
"INSERT INTO device_bind_history (device_id, child_id, bound_by_user_id, bind_source, bound_at) VALUES (:device_id, :child_id, :user_id, 2, CURRENT_TIMESTAMP)"
),
{"device_id": device_id, "child_id": child_id, "user_id": user_id},
)
self.commit()
return
elif existing_by_child:
# Child already bound to another device - update that binding
self.db.execute(
text(
"UPDATE device_bindings SET device_id = :device_id, status = 1, unbound_at = NULL WHERE child_id = :child_id AND status = 1"
),
{"device_id": device_id, "child_id": child_id},
)
# Skip INSERT since we updated
self.db.execute(
text(
"INSERT INTO device_bind_history (device_id, child_id, bound_by_user_id, bind_source, bound_at) VALUES (:device_id, :child_id, :user_id, 2, CURRENT_TIMESTAMP)"
),
{"device_id": device_id, "child_id": child_id, "user_id": user_id},
)
self.commit()
return
else:
# Insert new binding
self.db.execute(
text(
"INSERT INTO device_bindings (device_id, child_id, status, bound_at) VALUES (:device_id, :child_id, 1, CURRENT_TIMESTAMP)"
),
{"device_id": device_id, "child_id": child_id},
)
# Add history
self.db.execute(
text(
"INSERT INTO device_bind_history (device_id, child_id, bound_by_user_id, bind_source, bound_at) VALUES (:device_id, :child_id, :user_id, 2, CURRENT_TIMESTAMP)"
),
{"device_id": device_id, "child_id": child_id, "user_id": user_id},
)
self.commit()
def get_current_by_user(self, user_id: int) -> Optional[Mapping]:
return (
self.db.execute(
text(
"""
SELECT b.* FROM device_bindings b
JOIN children c ON b.child_id = c.child_id
WHERE c.parent_user_id = :user_id AND b.status = 1
ORDER BY b.bound_at DESC
LIMIT 1
"""
),
{"user_id": user_id},
)
.mappings()
.first()
)
def get_by_device(self, device_id: str, user_id: int) -> Optional[Mapping]:
return (
self.db.execute(
text(
"""
SELECT b.* FROM device_bindings b
JOIN children c ON b.child_id = c.child_id
WHERE b.device_id = :device_id AND c.parent_user_id = :user_id AND b.status = 1
"""
),
{"device_id": device_id, "user_id": user_id},
)
.mappings()
.first()
)
def unbind(self, device_id: str, user_id: int) -> bool:
row = self.get_by_device(device_id, user_id)
if not row:
return False
self.db.execute(
text("UPDATE device_bindings SET status = 0, unbound_at = CURRENT_TIMESTAMP WHERE id = :id"),
{"id": row["id"]},
)
self.db.execute(
text(
"INSERT INTO device_bind_history (device_id, child_id, bound_by_user_id, unbound_by_user_id, bind_source, bound_at, unbound_at, unbind_reason) SELECT device_id, child_id, bound_by_user_id, :user_id, bind_source, bound_at, CURRENT_TIMESTAMP, 'user_unbind' FROM device_bind_history WHERE device_id = :device_id AND unbound_at IS NULL"
),
{"device_id": device_id, "user_id": user_id},
)
self.commit()
return True
def list_history(self, device_id: str, limit: int = 20, cursor: datetime = None) -> list[Mapping]:
params = {"device_id": device_id, "limit": limit + 1}
where = "device_id = :device_id"
if cursor:
where += " AND bound_at < :cursor"
params["cursor"] = cursor
rows = (
self.db.execute(
text(
f"""
SELECT * FROM device_bind_history
WHERE {where}
ORDER BY bound_at DESC
LIMIT :limit
"""
),
params,
)
.mappings()
.all()
)
return rows