# 数据模型设计
## 🎯 学习目标
通过本章学习,您将能够:
- 理解ORM和数据模型的概念
- 掌握Python中的数据模型设计技巧
- 学会使用dataclass和SQLAlchemy等工具
- 在Chat-Room项目中应用数据模型最佳实践
## 🏗️ 数据模型架构
### 模型层次结构
```mermaid
graph TB
subgraph "数据模型层次"
A[业务模型
Business Models] --> B[数据访问层
Data Access Layer]
B --> C[数据库层
Database Layer]
A1[User Model] --> A
A2[Message Model] --> A
A3[Group Model] --> A
B1[UserRepository] --> B
B2[MessageRepository] --> B
B3[GroupRepository] --> B
C1[SQLite Database] --> C
end
subgraph "模型特性"
D[数据验证
Validation]
E[序列化
Serialization]
F[关系映射
Relationships]
G[缓存机制
Caching]
end
A --> D
A --> E
A --> F
A --> G
style A fill:#e8f5e8
style B fill:#fff3cd
style C fill:#f8d7da
```
### Chat-Room数据模型设计
```python
# shared/models/base_model.py - 基础模型类
from dataclasses import dataclass, field
from datetime import datetime
from typing import Optional, Dict, Any, List
import json
import uuid
@dataclass
class BaseModel:
"""
基础数据模型
提供所有模型的通用功能:
1. 序列化和反序列化
2. 数据验证
3. 字段映射
4. 时间戳管理
"""
created_at: datetime = field(default_factory=datetime.now)
updated_at: datetime = field(default_factory=datetime.now)
def to_dict(self) -> Dict[str, Any]:
"""转换为字典格式"""
result = {}
for field_name, field_value in self.__dict__.items():
if isinstance(field_value, datetime):
# 时间格式化
result[field_name] = field_value.isoformat()
elif isinstance(field_value, BaseModel):
# 嵌套模型
result[field_name] = field_value.to_dict()
elif isinstance(field_value, list):
# 列表处理
result[field_name] = [
item.to_dict() if isinstance(item, BaseModel) else item
for item in field_value
]
else:
result[field_name] = field_value
return result
def to_json(self) -> str:
"""转换为JSON字符串"""
return json.dumps(self.to_dict(), ensure_ascii=False, indent=2)
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'BaseModel':
"""从字典创建模型实例"""
# 过滤掉不存在的字段
field_names = {f.name for f in cls.__dataclass_fields__.values()}
filtered_data = {k: v for k, v in data.items() if k in field_names}
# 处理时间字段
for field_name, field_info in cls.__dataclass_fields__.items():
if field_name in filtered_data:
if field_info.type == datetime and isinstance(filtered_data[field_name], str):
filtered_data[field_name] = datetime.fromisoformat(filtered_data[field_name])
return cls(**filtered_data)
@classmethod
def from_json(cls, json_str: str) -> 'BaseModel':
"""从JSON字符串创建模型实例"""
data = json.loads(json_str)
return cls.from_dict(data)
def validate(self) -> List[str]:
"""
验证模型数据
Returns:
错误信息列表,空列表表示验证通过
"""
errors = []
# 子类可以重写此方法添加特定验证
return errors
def update_timestamp(self):
"""更新时间戳"""
self.updated_at = datetime.now()
@dataclass
class User(BaseModel):
"""
用户模型
封装用户相关的所有数据和行为
"""
user_id: Optional[int] = None
username: str = ""
password_hash: str = ""
email: Optional[str] = None
nickname: Optional[str] = None
avatar_url: Optional[str] = None
is_active: bool = True
is_banned: bool = False
last_login: Optional[datetime] = None
# 运行时属性(不存储到数据库)
is_online: bool = field(default=False, init=False)
current_groups: List[int] = field(default_factory=list, init=False)
def validate(self) -> List[str]:
"""用户数据验证"""
errors = super().validate()
# 用户名验证
if not self.username:
errors.append("用户名不能为空")
elif len(self.username) < 3 or len(self.username) > 20:
errors.append("用户名长度必须在3-20字符之间")
elif not self.username.replace('_', '').isalnum():
errors.append("用户名只能包含字母、数字和下划线")
# 密码哈希验证
if not self.password_hash:
errors.append("密码哈希不能为空")
# 邮箱验证
if self.email:
import re
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(email_pattern, self.email):
errors.append("邮箱格式不正确")
return errors
def get_display_name(self) -> str:
"""获取显示名称"""
return self.nickname if self.nickname else self.username
def is_valid_for_chat(self) -> bool:
"""检查是否可以参与聊天"""
return self.is_active and not self.is_banned
def to_public_dict(self) -> Dict[str, Any]:
"""转换为公开信息字典(不包含敏感信息)"""
return {
'user_id': self.user_id,
'username': self.username,
'nickname': self.nickname,
'avatar_url': self.avatar_url,
'is_online': self.is_online,
'last_login': self.last_login.isoformat() if self.last_login else None
}
@dataclass
class ChatGroup(BaseModel):
"""
聊天组模型
管理聊天组的信息和成员
"""
group_id: Optional[int] = None
group_name: str = ""
description: Optional[str] = None
owner_id: int = 0
max_members: int = 100
is_public: bool = True
# 运行时属性
member_count: int = field(default=0, init=False)
online_members: List[int] = field(default_factory=list, init=False)
def validate(self) -> List[str]:
"""聊天组数据验证"""
errors = super().validate()
# 群组名验证
if not self.group_name:
errors.append("群组名不能为空")
elif len(self.group_name) < 2 or len(self.group_name) > 50:
errors.append("群组名长度必须在2-50字符之间")
# 群主ID验证
if self.owner_id <= 0:
errors.append("群主ID无效")
# 最大成员数验证
if self.max_members < 2 or self.max_members > 1000:
errors.append("最大成员数必须在2-1000之间")
return errors
def is_full(self) -> bool:
"""检查群组是否已满"""
return self.member_count >= self.max_members
def can_join(self, user_id: int) -> bool:
"""检查用户是否可以加入"""
return not self.is_full() and user_id not in self.online_members
@dataclass
class Message(BaseModel):
"""
消息模型
处理各种类型的聊天消息
"""
message_id: Optional[int] = None
sender_id: int = 0
group_id: Optional[int] = None
receiver_id: Optional[int] = None
message_type: str = "text"
content: str = ""
file_path: Optional[str] = None
file_size: Optional[int] = None
is_deleted: bool = False
# 运行时属性
sender_name: str = field(default="", init=False)
is_read: bool = field(default=False, init=False)
def validate(self) -> List[str]:
"""消息数据验证"""
errors = super().validate()
# 发送者验证
if self.sender_id <= 0:
errors.append("发送者ID无效")
# 消息内容验证
if not self.content and self.message_type == "text":
errors.append("文本消息内容不能为空")
elif len(self.content) > 1000:
errors.append("消息内容不能超过1000字符")
# 消息类型验证
valid_types = ["text", "image", "file", "system"]
if self.message_type not in valid_types:
errors.append(f"消息类型必须是: {', '.join(valid_types)}")
# 聊天目标验证
if not self.group_id and not self.receiver_id:
errors.append("必须指定群组ID或接收者ID")
# 文件消息验证
if self.message_type in ["image", "file"]:
if not self.file_path:
errors.append("文件消息必须包含文件路径")
if not self.file_size or self.file_size <= 0:
errors.append("文件大小无效")
return errors
def is_private_message(self) -> bool:
"""检查是否为私聊消息"""
return self.receiver_id is not None and self.group_id is None
def is_group_message(self) -> bool:
"""检查是否为群组消息"""
return self.group_id is not None
def get_message_preview(self, max_length: int = 50) -> str:
"""获取消息预览"""
if self.message_type == "text":
if len(self.content) <= max_length:
return self.content
return self.content[:max_length] + "..."
elif self.message_type == "image":
return "[图片]"
elif self.message_type == "file":
return f"[文件: {self.file_path}]"
else:
return "[系统消息]"
@dataclass
class UserSession(BaseModel):
"""
用户会话模型
管理用户登录会话信息
"""
session_id: str = ""
user_id: int = 0
ip_address: str = ""
user_agent: str = ""
expires_at: datetime = field(default_factory=lambda: datetime.now())
is_active: bool = True
def validate(self) -> List[str]:
"""会话数据验证"""
errors = super().validate()
# 会话ID验证
if not self.session_id:
errors.append("会话ID不能为空")
elif len(self.session_id) < 16:
errors.append("会话ID长度不足")
# 用户ID验证
if self.user_id <= 0:
errors.append("用户ID无效")
# IP地址验证
if not self.ip_address:
errors.append("IP地址不能为空")
# 过期时间验证
if self.expires_at <= datetime.now():
errors.append("会话已过期")
return errors
def is_expired(self) -> bool:
"""检查会话是否过期"""
return datetime.now() > self.expires_at
def extend_session(self, hours: int = 24):
"""延长会话时间"""
from datetime import timedelta
self.expires_at = datetime.now() + timedelta(hours=hours)
self.update_timestamp()
# 模型工厂类
class ModelFactory:
"""
模型工厂
提供便捷的模型创建方法
"""
@staticmethod
def create_user(username: str, password_hash: str, **kwargs) -> User:
"""创建用户模型"""
return User(
username=username,
password_hash=password_hash,
**kwargs
)
@staticmethod
def create_group(group_name: str, owner_id: int, **kwargs) -> ChatGroup:
"""创建聊天组模型"""
return ChatGroup(
group_name=group_name,
owner_id=owner_id,
**kwargs
)
@staticmethod
def create_text_message(sender_id: int, content: str,
group_id: int = None, receiver_id: int = None) -> Message:
"""创建文本消息模型"""
return Message(
sender_id=sender_id,
content=content,
group_id=group_id,
receiver_id=receiver_id,
message_type="text"
)
@staticmethod
def create_file_message(sender_id: int, file_path: str, file_size: int,
group_id: int = None, receiver_id: int = None) -> Message:
"""创建文件消息模型"""
return Message(
sender_id=sender_id,
content=f"文件: {file_path}",
file_path=file_path,
file_size=file_size,
group_id=group_id,
receiver_id=receiver_id,
message_type="file"
)
@staticmethod
def create_session(session_id: str, user_id: int, ip_address: str, **kwargs) -> UserSession:
"""创建用户会话模型"""
from datetime import timedelta
return UserSession(
session_id=session_id,
user_id=user_id,
ip_address=ip_address,
expires_at=datetime.now() + timedelta(hours=24),
**kwargs
)
# 使用示例
def demo_data_models():
"""数据模型使用演示"""
print("=== 创建用户模型 ===")
user = ModelFactory.create_user(
username="alice",
password_hash="hashed_password",
email="alice@example.com",
nickname="Alice"
)
print(f"用户信息: {user.to_dict()}")
# 验证用户数据
errors = user.validate()
if errors:
print(f"验证错误: {errors}")
else:
print("用户数据验证通过")
print("\n=== 创建聊天组模型 ===")
group = ModelFactory.create_group(
group_name="技术讨论",
owner_id=1,
description="技术交流群组",
max_members=50
)
print(f"群组信息: {group.to_dict()}")
print("\n=== 创建消息模型 ===")
message = ModelFactory.create_text_message(
sender_id=1,
content="Hello everyone!",
group_id=1
)
print(f"消息信息: {message.to_dict()}")
print(f"消息预览: {message.get_message_preview()}")
print("\n=== JSON序列化 ===")
user_json = user.to_json()
print(f"用户JSON: {user_json}")
# 从JSON反序列化
user_from_json = User.from_json(user_json)
print(f"反序列化用户: {user_from_json.username}")
if __name__ == "__main__":
demo_data_models()
```
## 🎯 实践练习
### 练习1:扩展用户模型
```python
@dataclass
class ExtendedUser(User):
"""
扩展用户模型练习
要求:
1. 添加用户偏好设置
2. 添加用户统计信息
3. 实现用户等级系统
4. 添加社交关系
"""
preferences: Dict[str, Any] = field(default_factory=dict)
statistics: Dict[str, int] = field(default_factory=dict)
level: int = 1
experience: int = 0
friends: List[int] = field(default_factory=list)
def add_experience(self, points: int):
"""添加经验值"""
# TODO: 实现经验值和等级系统
pass
def add_friend(self, friend_id: int):
"""添加好友"""
# TODO: 实现好友关系管理
pass
```
### 练习2:消息搜索模型
```python
@dataclass
class MessageSearchQuery:
"""
消息搜索查询模型练习
要求:
1. 支持多种搜索条件
2. 实现搜索结果分页
3. 添加搜索历史
4. 优化搜索性能
"""
keyword: str = ""
sender_id: Optional[int] = None
group_id: Optional[int] = None
message_type: Optional[str] = None
date_from: Optional[datetime] = None
date_to: Optional[datetime] = None
page: int = 1
page_size: int = 20
def build_sql_query(self) -> str:
"""构建SQL查询"""
# TODO: 实现动态SQL构建
pass
def execute_search(self, db_connection) -> List[Message]:
"""执行搜索"""
# TODO: 实现搜索执行逻辑
pass
```
## ✅ 学习检查
完成本章学习后,请确认您能够:
- [ ] 理解数据模型的设计原则
- [ ] 使用dataclass创建数据模型
- [ ] 实现数据验证和序列化
- [ ] 设计模型之间的关系
- [ ] 应用模型工厂模式
- [ ] 完成实践练习
## 📚 下一步
数据模型设计掌握后,请继续学习:
- [第5章:多人聊天功能](../09-multi-user-chat/group-management.md)
---
**恭喜!您已经完成了数据库与用户系统的学习!** 🎉