# 第四章 架构设计 - 数据库设计 ## 4.1 数据库设计概述 项目使用 MySQL 8.0 作为主数据库,通过 Django ORM 进行数据访问。所有业务表继承 BaseModel 抽象基类,自动获得 6 个通用字段。 ## 4.2 通用字段规范 ### BaseModel 字段定义 所有业务表继承 `BaseModel`(`application/models.py`),自动包含以下字段: ```python class BaseModel(models.Model): """基础模型抽象类""" 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, db_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, db_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 ``` ### 字段说明 | 字段 | 类型 | 约束 | 默认值 | 说明 | |------|------|------|--------|------| | `id` | AutoField | PK, 自增 | - | 主键ID | | `create_user` | CharField(50) | NULL | None | 创建人用户名 | | `create_time` | DateTimeField | NULL | auto_now_add | 创建时间,自动填充 | | `update_user` | CharField(50) | NULL | None | 更新人用户名 | | `update_time` | DateTimeField | NULL | auto_now | 更新时间,自动刷新 | | `is_delete` | BooleanField | NOT NULL | 0 | 逻辑删除标识 | ### 字段自动管理 - `create_time`:使用 `auto_now_add=True`,仅在创建时自动设置当前时间 - `update_time`:使用 `auto_now=True`,每次保存时自动更新为当前时间 - `create_user` 和 `update_user`:需要业务代码手动赋值 ## 4.3 表名命名规范 ### 表前缀配置 所有业务表使用统一的前缀,通过 `config/env.py` 配置: ```python DATABASE_PREFIX = os.getenv('DATABASE_PREFIX', "django_") ``` ### 表名生成函数 使用 `get_table_name()` 函数自动生成带前缀的表名: ```python # utils/common.py def get_table_name(name): """获取带前缀的表名""" from config.env import DATABASE_PREFIX return f"{DATABASE_PREFIX}{name}" ``` ### 表名示例 | 模块 | 配置的表名 | 实际表名 | |------|-----------|----------| | 案例 | `get_table_name('example')` | `django_example` | | 用户 | `get_table_name('user')` | `django_user` | | 角色 | `get_table_name('role')` | `django_role` | | 菜单 | `get_table_name('menu')` | `django_menu` | | 部门 | `get_table_name('dept')` | `django_dept` | | 字典 | `get_table_name('dict')` | `django_dict` | ## 4.4 软删除设计 ### 设计原理 系统采用软删除策略,所有业务表包含 `is_delete` 字段: - `is_delete = 0`:正常记录(默认值) - `is_delete = 1`:已删除记录 ### 软删除实现 ```python # Service层删除操作 def delete_examples(instance_ids): """删除案例(批量删除)""" 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}条数据") ``` ### 查询过滤 所有查询都需要过滤已删除的记录: ```python # 分页查询 query = Example.objects.filter(is_delete=False) # 详情查询 instance = Example.objects.filter(id=id, is_delete=False).first() # 列表查询 queryset = Example.objects.filter(is_delete=False, status=1) ``` ### 软删除优势 | 优势 | 说明 | |------|------| | 数据可恢复 | 误删除的数据可以恢复 | | 审计追踪 | 保留删除记录的历史 | | 关联完整 | 不破坏外键关联关系 | | 操作安全 | 避免物理删除造成的数据丢失 | ## 4.5 字段顺序重排 ### 设计目标 通过 Django 的 `class_prepared` 信号,自动将 BaseModel 的公共字段移到业务字段之后,使数据库表结构更清晰。 ### 实现代码 ```python # application/models.py 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 # 检查是否有公共字段 has_base_fields = any( f.name in BASE_FIELD_NAMES for f in sender._meta.local_fields ) if not has_base_fields: 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) ``` ### 重排效果 ``` 重排前:id, create_user, create_time, name, status, sort, update_user, update_time, is_delete 重排后:name, status, sort, id, create_user, create_time, update_user, update_time, is_delete ``` ## 4.6 数据库表结构示例 ### 案例表 (django_example) ```sql CREATE TABLE `django_example` ( `name` varchar(100) NOT NULL COMMENT '案例名称', `avatar` varchar(255) DEFAULT NULL COMMENT '案例图片', `type` int NOT NULL COMMENT '案例类型:1-类型1 2-类型2 3-类型3 4-类型4', `status` int NOT NULL COMMENT '案例状态:1-正常 2-禁用', `sort` int NOT NULL COMMENT '排序', `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', `create_user` varchar(50) DEFAULT NULL COMMENT '创建人', `create_time` datetime(6) DEFAULT NULL COMMENT '创建时间', `update_user` varchar(50) DEFAULT NULL COMMENT '更新人', `update_time` datetime(6) DEFAULT NULL COMMENT '更新时间', `is_delete` tinyint(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除:0-正常 1-已删除', PRIMARY KEY (`id`), KEY `idx_example_name` (`name`) ) ENGINE=InnoDB COMMENT='案例表'; ``` ### 用户表 (django_user) ```sql CREATE TABLE `django_user` ( `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(128) NOT NULL COMMENT '密码', `realname` varchar(50) DEFAULT NULL COMMENT '真实姓名', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `email` varchar(100) DEFAULT NULL COMMENT '邮箱', `avatar` varchar(255) DEFAULT NULL COMMENT '头像', `status` int NOT NULL DEFAULT 1 COMMENT '状态:1-正常 2-禁用', `id` int NOT NULL AUTO_INCREMENT COMMENT '主键ID', `create_user` varchar(50) DEFAULT NULL COMMENT '创建人', `create_time` datetime(6) DEFAULT NULL COMMENT '创建时间', `update_user` varchar(50) DEFAULT NULL COMMENT '更新人', `update_time` datetime(6) DEFAULT NULL COMMENT '更新时间', `is_delete` tinyint(1) NOT NULL DEFAULT 0 COMMENT '逻辑删除:0-正常 1-已删除', PRIMARY KEY (`id`), UNIQUE KEY `idx_user_username` (`username`) ) ENGINE=InnoDB COMMENT='用户表'; ``` ## 4.7 字段类型映射 | Django 字段 | MySQL 类型 | 说明 | |-------------|-----------|------| | `AutoField` | `INT AUTO_INCREMENT` | 自增主键 | | `CharField` | `VARCHAR(n)` | 变长字符串 | | `TextField` | `LONGTEXT` | 长文本 | | `IntegerField` | `INT` | 整数 | | `BooleanField` | `TINYINT(1)` | 布尔值 | | `DateTimeField` | `DATETIME(6)` | 日期时间 | | `DecimalField` | `DECIMAL(p,s)` | 精确小数 | | `ImageField` | `VARCHAR(255)` | 图片路径 | | `ForeignKey` | `INT` + 外键约束 | 外键关联 | ## 4.8 索引设计规范 ### 自动索引 - 主键 `id` 自动创建主键索引 - 唯一约束字段自动创建唯一索引 ### 手动索引 在模型定义中使用 `db_index=True` 为常用查询字段创建索引: ```python name = models.CharField( max_length=100, db_index=True, # 创建索引 verbose_name="案例名称" ) ``` ### 索引命名规范 ``` idx_<表名>_<字段名> ``` 示例:`idx_example_name`、`idx_user_username` ## 4.9 数据库迁移 ### 生成迁移文件 ```bash python manage.py makemigrations ``` ### 执行迁移 ```bash python manage.py migrate ``` ### 代码生成器自动迁移 代码生成器在生成新模块时会自动执行迁移: ```python # generator_config.py os.system('python manage.py makemigrations') os.system('python manage.py migrate') ```