1. 项目概述
最近在重构一个老项目的用户系统时,我深刻体会到了Django自定义用户体系的"痛并快乐着"。这个原本看似简单的需求,在实际开发中却遇到了各种意想不到的坑。今天我就把这段经历整理成文,分享给同样在Django自定义用户体系下开发业务模块的同仁们。
这个项目背景是一个已有5年历史的SaaS平台,最初采用Django默认的User模型,随着业务发展逐渐暴露出扩展性不足的问题。我们需要在不影响现有业务的前提下,将用户系统迁移到自定义模型,同时保证所有关联业务模块的正常运行。
2. 核心需求解析
2.1 为什么需要自定义用户体系
Django自带的User模型虽然开箱即用,但在实际业务场景中往往捉襟见肘。在我们的案例中,主要面临三个核心痛点:
- 用户属性扩展需求:需要存储企业用户的营业执照号、联系人等信息
- 多类型用户并存:平台同时存在个人用户和企业用户,权限体系不同
- 第三方登录集成:需要支持微信、企业微信等多种登录方式
2.2 技术选型考量
在自定义用户模型实现上,我们评估了三种主流方案:
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 继承AbstractUser | 保留全部默认功能 | 需要早期规划,后期迁移成本高 | 新项目首选 |
| 继承AbstractBaseUser | 完全自定义 | 需要重写大量基础功能 | 特殊认证需求 |
| OneToOne扩展 | 兼容现有系统 | 查询效率低,代码冗余 | 旧系统改造 |
最终我们选择了AbstractUser方案,虽然需要数据迁移,但从长期维护角度看最合理。
3. 实现过程详解
3.1 自定义用户模型定义
python复制from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomUser(AbstractUser):
USER_TYPE_CHOICES = (
(1, '个人用户'),
(2, '企业用户'),
)
user_type = models.PositiveSmallIntegerField(choices=USER_TYPE_CHOICES)
mobile = models.CharField(max_length=20, unique=True)
company_name = models.CharField(max_length=100, blank=True)
license_number = models.CharField(max_length=50, blank=True)
# 必须设置,用于指定自定义用户模型
class Meta:
db_table = 'custom_user'
def __str__(self):
return self.username
关键点说明:
- 必须设置AUTH_USER_MODEL指向自定义模型
- 首次迁移前必须完成模型定义,否则会锁死默认User模型
- 建议保留username字段,虽然可以重写但会引入复杂度
3.2 业务模块适配改造
所有ForeignKey到User的模型都需要修改:
python复制# 修改前
from django.contrib.auth.models import User
class Order(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
# 修改后
from django.conf import settings
from django.db import models
class Order(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
重要提示:不要直接导入User模型,必须通过settings.AUTH_USER_MODEL引用
3.3 数据迁移策略
我们采用分阶段迁移方案:
- 先创建CustomUser表并保持独立
- 编写数据同步脚本,逐步迁移用户数据
- 设置双写机制,确保过渡期数据一致性
- 最终切换AUTH_USER_MODEL指向
python复制# 示例数据迁移脚本
from django.contrib.auth import get_user_model
from legacy_app.models import LegacyUser
def migrate_users():
User = get_user_model()
for legacy_user in LegacyUser.objects.all():
User.objects.create(
username=legacy_user.login_name,
# 其他字段映射...
)
4. 常见问题与解决方案
4.1 管理员后台兼容问题
自定义用户后,admin界面可能出现以下问题:
- 用户列表不显示自定义字段
- 解决方案:自定义UserAdmin
python复制from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import CustomUser
class CustomUserAdmin(UserAdmin):
list_display = ('username', 'email', 'user_type', 'mobile')
fieldsets = UserAdmin.fieldsets + (
('扩展信息', {'fields': ('user_type', 'mobile', 'company_name')}),
)
admin.site.register(CustomUser, CustomUserAdmin)
- 创建用户表单缺少字段
- 解决方案:扩展UserCreationForm
4.2 第三方应用兼容性
常见问题:
- Django REST framework的Token认证失效
- allauth社交登录不工作
- django-guardian权限系统异常
通用解决模式:
- 检查应用是否支持自定义用户模型
- 查找是否有AUTH_USER_MODEL相关配置项
- 必要时继承并重写相关视图和序列化器
4.3 性能优化技巧
- 避免使用select_related('user')的N+1查询
- 改用prefetch_related或annotate必要字段
- 高频访问的用户属性考虑冗余存储
- 用户类型判断优化:
python复制# 不推荐
if user.user_type == 1:
# 个人用户逻辑
# 推荐
def is_personal_user(user):
return user.user_type == 1
5. 最佳实践总结
经过这次项目,我总结了以下经验:
- 尽早决定是否自定义用户模型,越晚成本越高
- 保持与Django认证框架的兼容性,不要过度自定义
- 编写全面的单元测试,覆盖各种用户场景
- 文档化所有自定义部分,方便后续维护
- 考虑使用django-polymorphic处理多类型用户
对于新项目,我的建议是:
- 即使暂时不需要,也先继承AbstractUser预留扩展能力
- 用户相关业务逻辑尽量通过信号或自定义管理器实现
- 用户属性变更要考虑缓存失效策略
最后分享一个实用技巧:在开发阶段可以使用django-debug-toolbar监控所有用户相关查询,及时发现N+1问题。
