452 lines
12 KiB
Markdown
452 lines
12 KiB
Markdown
# 第四章 架构设计 - 分层架构详解
|
||
|
||
## 4.1 分层架构概述
|
||
|
||
项目采用经典的五层架构设计,各层职责明确,依赖关系清晰:
|
||
|
||
```
|
||
+------------------+
|
||
| View Layer | <-- 视图层:接收请求,返回响应
|
||
+------------------+
|
||
|
|
||
v
|
||
+------------------+
|
||
| Service Layer | <-- 服务层:业务逻辑处理
|
||
+------------------+
|
||
|
|
||
v
|
||
+------------------+
|
||
| Form Layer | <-- 表单层:数据验证
|
||
+------------------+
|
||
|
|
||
v
|
||
+------------------+
|
||
| Model Layer | <-- 模型层:数据库操作
|
||
+------------------+
|
||
|
|
||
v
|
||
+------------------+
|
||
| URL Layer | <-- 路由层:URL映射
|
||
+------------------+
|
||
```
|
||
|
||
## 4.2 模型层 (Model Layer)
|
||
|
||
### 4.2.1 BaseModel 抽象基类
|
||
|
||
所有业务模型继承自 `BaseModel`(`application/models.py`),自动获得 6 个公共字段:
|
||
|
||
```python
|
||
class BaseModel(models.Model):
|
||
"""基础模型抽象类"""
|
||
|
||
# 主键ID字段
|
||
id = models.AutoField(
|
||
auto_created=True,
|
||
primary_key=True,
|
||
serialize=False,
|
||
verbose_name='主键ID',
|
||
db_comment='主键ID'
|
||
)
|
||
|
||
# 创建人字段
|
||
create_user = models.CharField(
|
||
null=True,
|
||
max_length=50,
|
||
default=None,
|
||
verbose_name='创建人',
|
||
db_comment='创建人'
|
||
)
|
||
|
||
# 创建时间字段
|
||
create_time = models.DateTimeField(
|
||
null=True,
|
||
auto_now_add=True,
|
||
verbose_name="创建时间",
|
||
db_comment='创建时间'
|
||
)
|
||
|
||
# 更新人字段
|
||
update_user = models.CharField(
|
||
null=True,
|
||
max_length=50,
|
||
default=None,
|
||
verbose_name='更新人',
|
||
db_comment='更新人'
|
||
)
|
||
|
||
# 更新时间字段
|
||
update_time = models.DateTimeField(
|
||
null=True,
|
||
auto_now=True,
|
||
verbose_name="更新时间",
|
||
db_comment='更新时间'
|
||
)
|
||
|
||
# 逻辑删除标识
|
||
is_delete = models.BooleanField(
|
||
default=0,
|
||
db_default=0,
|
||
verbose_name="逻辑删除",
|
||
db_comment='逻辑删除:0-正常 1-已删除'
|
||
)
|
||
|
||
class Meta:
|
||
abstract = True # 抽象模型,不创建数据库表
|
||
```
|
||
|
||
### 4.2.2 业务模型示例
|
||
|
||
以案例模型为例(`application/example/models.py`):
|
||
|
||
```python
|
||
from application.models import BaseModel
|
||
from utils.common import get_table_name
|
||
|
||
class Example(BaseModel):
|
||
"""案例模型类"""
|
||
|
||
name = models.CharField(
|
||
null=False, max_length=100, db_index=True,
|
||
verbose_name="案例名称", db_comment='案例名称'
|
||
)
|
||
|
||
avatar = models.CharField(
|
||
null=True, blank=True, max_length=255,
|
||
verbose_name="案例图片", db_comment='案例图片'
|
||
)
|
||
|
||
TYPE_CHOICES = ((1, "类型1"), (2, "类型2"), (3, "类型3"), (4, "类型4"))
|
||
|
||
type = models.IntegerField(
|
||
null=False, choices=TYPE_CHOICES,
|
||
verbose_name="案例类型", db_comment='案例类型'
|
||
)
|
||
|
||
STATUS_CHOICES = ((1, "正常"), (2, "禁用"))
|
||
|
||
status = models.IntegerField(
|
||
null=False, choices=STATUS_CHOICES,
|
||
verbose_name="案例状态", db_comment='案例状态:1-正常 2-禁用'
|
||
)
|
||
|
||
sort = models.IntegerField(
|
||
null=False, verbose_name="排序", db_comment='排序'
|
||
)
|
||
|
||
class Meta:
|
||
db_table = get_table_name('example') # 自动生成表名
|
||
db_table_comment = "案例表"
|
||
ordering = ("sort",)
|
||
```
|
||
|
||
### 4.2.3 字段顺序重排
|
||
|
||
通过 `class_prepared` 信号,自动将公共字段移到业务字段之后:
|
||
|
||
```python
|
||
BASE_FIELD_NAMES = ['id', 'create_user', 'create_time',
|
||
'update_user', 'update_time', 'is_delete']
|
||
|
||
def _reorder_fields(sender, **kwargs):
|
||
"""信号处理函数:重排模型字段顺序"""
|
||
if not hasattr(sender, '_meta') or sender._meta.abstract:
|
||
return
|
||
|
||
base_fields = []
|
||
business_fields = []
|
||
|
||
for field in sender._meta.local_fields:
|
||
if field.name in BASE_FIELD_NAMES:
|
||
base_fields.append(field)
|
||
else:
|
||
business_fields.append(field)
|
||
|
||
# 重新排列:业务字段在前,公共字段在后
|
||
sender._meta.local_fields = business_fields + base_fields
|
||
|
||
class_prepared.connect(_reorder_fields)
|
||
```
|
||
|
||
## 4.3 表单层 (Form Layer)
|
||
|
||
### 4.3.1 ModelForm 验证
|
||
|
||
表单层使用 Django 的 `ModelForm` 进行数据验证(`application/example/forms.py`):
|
||
|
||
```python
|
||
from django import forms
|
||
from application.example import models
|
||
|
||
class ExampleForm(forms.ModelForm):
|
||
"""案例表单验证类"""
|
||
|
||
name = forms.CharField(
|
||
required=True, max_length=100,
|
||
error_messages={
|
||
'required': '案例名称不能为空',
|
||
'max_length': '案例名称长度不得超过100个字符',
|
||
}
|
||
)
|
||
|
||
type = forms.IntegerField(
|
||
required=True, min_value=1, max_value=4,
|
||
error_messages={
|
||
'required': '案例类型不能为空',
|
||
'min_value': '案例类型值不能小于1',
|
||
'max_value': '案例类型值不能大于4',
|
||
}
|
||
)
|
||
|
||
status = forms.IntegerField(
|
||
required=True, min_value=1, max_value=2,
|
||
error_messages={
|
||
'required': '案例状态不能为空',
|
||
}
|
||
)
|
||
|
||
sort = forms.IntegerField(
|
||
required=True,
|
||
error_messages={'required': '排序不能为空'}
|
||
)
|
||
|
||
class Meta:
|
||
model = models.Example
|
||
fields = ['name', 'avatar', 'type', 'status', 'sort']
|
||
```
|
||
|
||
### 4.3.2 验证错误处理
|
||
|
||
```python
|
||
# 在Service层使用表单验证
|
||
form = ExampleForm(data)
|
||
if not form.is_valid():
|
||
# 获取第一个错误信息
|
||
return R.failed(msg=regular.get_err(form))
|
||
```
|
||
|
||
## 4.4 服务层 (Service Layer)
|
||
|
||
### 4.4.1 服务函数命名规范
|
||
|
||
服务层函数遵循统一的命名规范:
|
||
|
||
| 函数类型 | 命名格式 | 示例 |
|
||
|----------|----------|------|
|
||
| 分页查询 | `get_<module>_page` | `get_example_page` |
|
||
| 列表查询 | `get_<module>_list` | `get_example_list` |
|
||
| 详情查询 | `get_<module>_detail` | `get_example_detail` |
|
||
| 添加 | `add_<module>` | `add_example` |
|
||
| 更新 | `update_<module>` | `update_example` |
|
||
| 删除 | `delete_<module>s` | `delete_examples` |
|
||
| 状态更新 | `update_<module>_status` | `update_example_status` |
|
||
|
||
### 4.4.2 分页查询实现
|
||
|
||
```python
|
||
def get_example_page(request):
|
||
"""查询案例分页数据"""
|
||
try:
|
||
page_no = int(request.GET.get('pageNo', 1))
|
||
page_size = int(request.GET.get('pageSize', PAGE_SIZE))
|
||
|
||
# 构建基础查询(只查询未删除的记录)
|
||
query = Example.objects.filter(is_delete=False)
|
||
query = _apply_filters(query, request)
|
||
query = query.order_by("sort")
|
||
|
||
# 执行分页查询
|
||
paginator = Paginator(query, page_size)
|
||
page_list = paginator.page(page_no)
|
||
|
||
# 构建并返回分页数据
|
||
page_data = _build_page_data(page_list, paginator, page_no, page_size)
|
||
return R.ok(data=page_data)
|
||
except Exception as e:
|
||
logging.error(f"查询案例分页数据异常: {str(e)}")
|
||
return R.failed(msg="查询失败,请稍后重试")
|
||
```
|
||
|
||
### 4.4.3 添加操作实现
|
||
|
||
```python
|
||
def add_example(request):
|
||
"""添加案例"""
|
||
data, error = parse_request_body(request)
|
||
if error:
|
||
return R.failed(msg=error)
|
||
|
||
form = ExampleForm(data)
|
||
if not form.is_valid():
|
||
return R.failed(msg=regular.get_err(form))
|
||
|
||
try:
|
||
cleaned_data = form.cleaned_data
|
||
avatar = cleaned_data.get('avatar')
|
||
if avatar:
|
||
avatar = save_file(avatar, "example")
|
||
|
||
Example.objects.create(
|
||
name=cleaned_data.get('name'),
|
||
avatar=avatar,
|
||
type=cleaned_data.get('type'),
|
||
status=cleaned_data.get('status'),
|
||
sort=cleaned_data.get('sort'),
|
||
create_user=get_username(request),
|
||
update_user=get_username(request)
|
||
)
|
||
return R.ok(msg="创建成功")
|
||
except Exception as e:
|
||
logging.error(f"添加案例异常: {str(e)}")
|
||
return R.failed(msg="添加失败,请稍后重试")
|
||
```
|
||
|
||
### 4.4.4 删除操作实现(软删除)
|
||
|
||
```python
|
||
def delete_examples(instance_ids):
|
||
"""删除案例(批量删除)"""
|
||
if not instance_ids:
|
||
return R.failed("记录ID不存在")
|
||
|
||
try:
|
||
id_list = [int(id_str.strip())
|
||
for id_str in instance_ids.split(',') if id_str.strip()]
|
||
|
||
# 批量更新删除标识(逻辑删除)
|
||
updated_count = Example.objects.filter(
|
||
id__in=id_list, is_delete=False
|
||
).update(is_delete=True)
|
||
|
||
return R.ok(msg=f"本次共删除{updated_count}条数据")
|
||
except ValueError:
|
||
return R.failed("记录ID格式错误")
|
||
```
|
||
|
||
## 4.5 视图层 (View Layer)
|
||
|
||
### 4.5.1 视图类结构
|
||
|
||
视图层使用 Django 的类视图,结合装饰器和混入类实现认证和权限控制:
|
||
|
||
```python
|
||
from django.utils.decorators import method_decorator
|
||
from django.views import View
|
||
from middleware.login_middleware import check_login
|
||
from middleware.permission_middleware import PermissionRequired
|
||
|
||
@method_decorator(check_login, name="get")
|
||
class ExamplePageView(PermissionRequired, View):
|
||
"""案例分页数据查询视图"""
|
||
|
||
permission_required = ("sys:example:page",)
|
||
|
||
@operation_log(title="案例管理-查询分页列表", log_type=LogType.QUERY)
|
||
def get(self, request):
|
||
result = services.get_example_page(request)
|
||
return result
|
||
```
|
||
|
||
### 4.5.2 视图类职责
|
||
|
||
| 职责 | 说明 |
|
||
|------|------|
|
||
| 请求接收 | 接收 HTTP 请求,提取参数 |
|
||
| 认证检查 | 通过 `@check_login` 装饰器验证登录状态 |
|
||
| 权限检查 | 通过 `PermissionRequired` 混入类验证操作权限 |
|
||
| 参数校验 | 简单的参数格式校验(如 ID 有效性) |
|
||
| 服务调用 | 调用 Service 层函数处理业务逻辑 |
|
||
| 响应返回 | 返回 Service 层返回的响应结果 |
|
||
|
||
## 4.6 路由层 (URL Layer)
|
||
|
||
### 4.6.1 模块路由定义
|
||
|
||
每个模块在 `urls.py` 中定义自己的路由:
|
||
|
||
```python
|
||
# application/example/urls.py
|
||
from django.urls import path
|
||
from application.example import views
|
||
|
||
urlpatterns = [
|
||
path('page', views.ExamplePageView.as_view()),
|
||
path('list', views.ExampleListView.as_view()),
|
||
path('detail/<int:id>', views.ExampleDetailView.as_view()),
|
||
path('add', views.ExampleAddView.as_view()),
|
||
path('update', views.ExampleUpdateView.as_view()),
|
||
path('delete/<str:id>', views.ExampleDeleteView.as_view()),
|
||
path('status', views.ExampleStatusView.as_view()),
|
||
]
|
||
```
|
||
|
||
### 4.6.2 主路由注册
|
||
|
||
所有模块路由在主路由中注册(`application/urls.py`):
|
||
|
||
```python
|
||
from django.urls import path, include
|
||
|
||
urlpatterns = [
|
||
path('admin/', admin.site.urls),
|
||
path('', include('application.login.urls')),
|
||
path('index/', include('application.index.urls')),
|
||
path('example/', include('application.example.urls')),
|
||
# ... 其他模块
|
||
]
|
||
```
|
||
|
||
## 4.7 数据流向
|
||
|
||
### 4.7.1 请求数据流
|
||
|
||
```
|
||
HTTP Request
|
||
|
|
||
v
|
||
View.get/post/put/delete
|
||
|
|
||
v
|
||
Service函数
|
||
|
|
||
v
|
||
parse_request_body(request) --> 解析JSON请求体
|
||
|
|
||
v
|
||
Form验证 --> 数据校验
|
||
|
|
||
v
|
||
Model操作 --> 数据库读写
|
||
|
|
||
v
|
||
构建返回数据字典
|
||
|
|
||
v
|
||
R.ok()/R.failed() --> 构造响应
|
||
|
|
||
v
|
||
JsonResponse --> HTTP Response
|
||
```
|
||
|
||
### 4.7.2 响应数据格式转换
|
||
|
||
```python
|
||
# Model对象 -> 字典
|
||
record = {
|
||
'id': item.id,
|
||
'name': item.name,
|
||
'avatar': get_file_url(item.avatar),
|
||
'createUser': item.create_user, # snake_case -> camelCase
|
||
'createTime': format_datetime(item.create_time),
|
||
}
|
||
|
||
# 字典 -> JSON
|
||
return R.ok(data=record)
|
||
# {
|
||
# "code": 0,
|
||
# "data": {"id": 1, "name": "...", "createUser": "admin"},
|
||
# "msg": "操作成功",
|
||
# "ok": true
|
||
# }
|
||
```
|