from django.contrib.auth.models import User
Django的Auth认证系统中提供了用户模型类User保存用户的数据,默认的User包含以下常见的基本字段:
| 字段名 | 字段描述 |
|---|---|
username | 必选。150个字符以内。 用户名可能包含字母数字,_,@,+ . 和-个字符。 |
first_name | 可选(blank=True)。 少于等于30个字符。 |
last_name | 可选(blank=True)。 少于等于30个字符。 |
email | 可选(blank=True)。 邮箱地址。 |
password | 必选。 密码的哈希加密串。 (Django 不保存原始密码)。 原始密码可以无限长而且可以包含任意字符。 |
groups | 与Group 之间的多对多关系。对接权限功能的。 |
user_permissions | 与Permission 之间的多对多关系。对接权限功能的。 |
is_staff | 布尔值。 设置用户是否可以访问Admin 站点。 |
is_active | 布尔值。 指示用户的账号是否激活。 它不是用来控制用户是否能够登录,而是描述一种帐号的使用状态。值为False的时候,是无法登录的。 |
is_superuser | 是否是超级用户。超级用户具有所有权限。 |
last_login | 用户最后一次登录的时间。 |
date_joined | 账户创建的时间。 当账号创建时,默认设置为当前的date/time。 |
模型提供的常用方法:
模型常用方法可以通过user实例对象.方法名来进行调用。
-
set_password(raw_password)设置用户的密码为给定的原始字符串,并负责密码的。 不会保存
User对象。当None为raw_password时,密码将设置为一个不可用的密码。 -
check_password(raw_password)如果给定的raw_password是用户的真实密码,则返回True,可以在校验用户密码时使用。
管理器的常用方法:
管理器方法可以通过User.objects. 进行调用。
-
create_user(username, email=None, password=None, **extra_fields)创建、保存并返回一个
User对象。 -
create_superuser(username, email, password, **extra_fields)与
create_user()相同,但是设置is_staff和is_superuser为True。
虽然上面的User模型看起来很多的属性和方法了,但是我们当前要实现的项目还需要记录用户的手机号,或者头像等等一系列信息。所以我们需要在原有模型的基础上对这个模型进行改造。
扩展User
代理模型(Proxy Model)
适用场景
- 不改数据库结构
- 只加方法或改默认行为(排序、管理器)
- 适合给用户模型打补丁
操作步骤
from django.contrib.auth.models import User
class VIPUser(User): # 继承默认用户模型
class Meta:
proxy = True # 关键!声明代理模型
ordering = ['-date_joined'] # VIP按注册时间倒序
def check_privilege(self):
"""VIP专属方法:检查特权状态"""
return self.is_staff and self.is_active
# 自定义管理器示例
class VIPManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(is_staff=True)
使用方法:
# 查询VIP用户
vip_users = VIPUser.objects.filter(is_active=True)
print(vip_users.first().check_privilege())
优点:
- 零数据库迁移
- 无缝兼容现有代码
缺点:
- 不能新增字段
- 功能扩展有限
一对一关联(User Profile) - 最常用方案
适用场景
- 添加与认证无关的扩展信息(头像、地址等)
- 需要保持用户表简洁
完整实现
# models.py
from django.db import models
from django.contrib.auth.models import User
from django.db.models.signals import post_save #导入post_save信号
from django.dispatch import receiver # 导入receiver监听信号
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
bio = models.TextField('个人简介', blank=True)
wechat = models.CharField('微信', max_length=50, blank=True)
avatar = models.ImageField('头像', upload_to='avatars/', blank=True)
telephone = models.CharField(max_length=11,verbose_name="手机号码")
@receiver(post_save,sender=User) # 监听到post_save事件且发送者是User则执行create_extension_user函数
def create_extension_user(sender,instance,created,**kwargs):
"""
sender:发送者
instance:save对象
created:是否是创建数据
"""
if created:
# 如果创建对象,ExtensionUser进行绑定
ExtensionUser.objects.create(user=instance)
else:
# 如果不是创建对象,同样将改变进行保存
instance.extension.save()
这样,我们就可以使用User.userprofile来访问扩展模型的字段和方法。
user = User.objects.get(username='john')
bio = user.userprofile.bio
在views.py文件中为新创建的user扩展表添加一条数据,示例代码如下:
from django.shortcuts import render
from django.http import HttpResponse
from django.contrib.auth.models import User
from .models import UserExtension
<!--1. 添加一条数据-->
def one_to_one(request):
user = User.objects.create_user(username='孤烟逐云', email='111111@qq.com', password='111111')
user.extension.telephone = '18833332222'
user.save()
return render(request, 'one_to_one.html')
自定义登录的验证函数,采用扩展模型中的telephone和password字段验证。示例代码如下:
def my_authenticate(telephone, password):
user = User.objects.filter(extension__telephone=telephone).first()
if user:
<!--如果该手机号的用户存在,再判断输入的密码是否正确-->
is_true = user.check_password(password)
if is_true:
return user
print('您查找的用户是:%s' % user.username)
else:
return None
else:
return None
调用定义好的登录验证函数,进行用户的验证,示例代码如下:
def one_to_one(request):
telephone = request.GET.get('telephone')
password = request.GET.get('password')
user = my_authenticate(telephone, password)
if user:
print('您查找的用户是:%s' % user.username)
context = {
'user': user
}
return render(request, 'one_to_one.html', context=context)
else:
context = {
'user': '您查找的用户不存在!'
}
return render(request, 'one_to_one.html', context=context)
在one_to_one.html中接收视图函数传递的上下文,示例代码如下:
<ul>
<li>用户名:{{ user.username }}</li>
<li>手机号{{ user.extension.telephone }}</li>
</ul>
优化:
# 使用select_related避免N+1查询
users = User.objects.select_related('profile').filter(is_active=True)[:10]
优点:
- 不影响核心用户表
- 灵活扩展
- 易维护
缺点:
- 需要处理关联查询
- 信号机制需要测试
继承AbstractUser - 最佳平衡方案
适用场景
- 需要直接修改用户模型
- 早期项目阶段
- 添加与认证相关的字段(手机号等)
完整实现
Django提供了django.contrib.auth.models.AbstractUser用户抽象模型类允许我们继承,扩展字段来使用Django认证系统的用户模型类。
(创建新的user子应用,可忽略)
创建user app
python manage.py startapp users在setting.py文件中注册子应用。
INSTALLED_APPS = [ ... 'users', ]创建users/urls.py子路由并在总路由中进行注册。
users/urls.py,代码:
from django.urls import path from . import views urlpatterns = [ ]urls.py,总路由,代码:
from django.contrib import admin from django.urls import path,re_path,include from django.conf import settings urlpatterns = [ path('admin/', admin.site.urls), path("users/", include("users.urls")), ]我们可以在apps中创建Django应用users,并在配置文件中注册users应用。
在创建好的应用models.py中定义用户的用户模型类。
from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.
class User(AbstractUser):
mobile = models.CharField(max_length=15, unique=True, verbose_name='手机号')
money = models.DecimalField(max_digits=9, default=0.0, decimal_places=2, verbose_name="钱包余额")
credit = models.IntegerField(default=0, verbose_name="积分")
avatar = models.ImageField(upload_to="avatar/%Y", null=True, default="", verbose_name="个人头像")
nickname = models.CharField(max_length=50, default="", null=True, verbose_name="用户昵称")
class Meta:
db_table = 'lf_users'
verbose_name = '用户信息'
verbose_name_plural = verbose_name
我们自定义的用户模型类还不能直接被Django的认证系统所识别,需要在配置文件中告知Django认证系统使用我们自定义的模型类。
在settings.py配置文件中进行设置
AUTH_USER_MODEL = 'users.User'
注意:Django建议我们对于AUTH_USER_MODEL参数的设置一定要在第一次数据库迁移之前就设置好,否则后续使用可能出现未知错误。

这是表示有一个叫admin的子应用使用了原来的废弃的auth.User模型,但是目前数据库已经设置了默认的子应用为users的模型了,所以产生了冲突。那么这种冲突,我们需要重置下原来的auth模块的迁移操作,再次迁移就可以解决了。
解决步骤:
1. 备份数据库[如果刚开始开发,无需备份。]
mysqldump -uroot -p123 库名 > ${date}_库名.sql
2. 注释掉users.User代码以及AUTH_USER_MODEL配置项,然后执行数据迁移回滚操作,把冲突的所有表迁移记录全部归零
cd ~/Desktop/项目路径/
# python manage.py migrate <子应用目录> zero
python manage.py migrate auth zero
3. 恢复users.User代码以及AUTH_USER_MODEL配置项,执行数据迁移。
python manage.py makemigrations
python manage.py migrate
4. 创建管理员查看auth功能是否能正常使用。
python manage.py createsuperuser
注意事项
1. 必须项目初始化时设置
2. 数据库需要完全迁移
3. 第三方应用可能不兼容
Admin配置
# admin.py
from django.contrib.auth.admin import UserAdmin
class CustomUserAdmin(UserAdmin):
list_display = ('mobile', 'username', 'company', 'is_staff')
fieldsets = (
(None, {'fields': ('mobile', 'password')}),
('个人信息', {'fields': ('username', 'company')}),
('权限', {'fields': ('is_active', 'is_staff')}),
)
admin.site.register(CustomUser, CustomUserAdmin)
继承AbstractBaseUser - 终极自定义方案
继承AbstracUser类自定义User有一个不好的地方,就是我们没有办法改变其已有的字段,比如first_name和last_name我们并不需要,这个时候就可以继承AbstractBaseUser和PermissionsMixin类完全重写User模型:
适用场景
- 完全重构认证逻辑
- 使用非标准登录方式(邮箱/手机号)
- 需要深度控制用户模型
完整实现
AbstractUser的源代码
class AbstractUser(AbstractBaseUser, PermissionsMixin):
"""
An abstract base class implementing a fully featured User model with
admin-compliant permissions.
Username and password are required. Other fields are optional.
"""
username_validator = UnicodeUsernameValidator()
username = models.CharField(
_('username'),
max_length=150,
unique=True,
help_text=_('Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.'),
validators=[username_validator],
error_messages={
'unique': _("A user with that username already exists."),
},
)
first_name = models.CharField(_('first name'), max_length=30, blank=True)
last_name = models.CharField(_('last name'), max_length=150, blank=True)
email = models.EmailField(_('email address'), blank=True)
is_staff = models.BooleanField(
_('staff status'),
default=False,
help_text=_('Designates whether the user can log into this admin site.'),
)
is_active = models.BooleanField(
_('active'),
default=True,
help_text=_(
'Designates whether this user should be treated as active. '
'Unselect this instead of deleting accounts.'
),
)
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
objects = UserManager()
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
abstract = True
def clean(self):
super().clean()
self.email = self.__class__.objects.normalize_email(self.email)
def get_full_name(self):
"""
Return the first_name plus the last_name, with a space in between.
"""
full_name = '%s %s' % (self.first_name, self.last_name)
return full_name.strip()
def get_short_name(self):
"""Return the short name for the user."""
return self.first_name
def email_user(self, subject, message, from_email=None, **kwargs):
"""Send an email to this user."""
send_mail(subject, message, from_email, [self.email], **kwargs)
以上代码中同时设置了一些字段指定行为和一个对象指定行为:
objects = UserManager()
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
AbstractBaseUser和PermissionsMixin分别定义了用户类最基本的字段以及最基本的权限字段,可以说这两个类是Django中user类最核心的部分了,缩无可缩。
查看AbstractBaseUser的源代码,你会发现这个类中定义了如下几个字段:
- password,密码
- last_login,最后登录时间
而在PermissionsMixin中,定义了如下几个字段:
- is_super,是否是超级管理员
- groups,所属用户组
- user_permissions,用户权限组
由此可见,使用AbstractBaseUser和PermissionsMixin自定义用户类的自由度是最高的,只需要继承5个字段即可,其他的可以完全由我们自定义。但使用这两个类定义用户组的时候需要在自定义类中额外配置一些信息。
定义一个自定义用户类,要求使用手机号作为用户名进行注册,去除邮箱、姓和名字段,增加昵称,真实姓名,用户头像,出生年月,性别,创建时间等字段。
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
class Users(AbstractBaseUser, PermissionsMixin):
username = models.CharField(verbose_name="用户名", primary_key=True, max_length=20, unique=True)
nickname = models.CharField(verbose_name="昵称", max_length=20, blank=True, null=True)
real_name = models.CharField(verbose_name="真实姓名", max_length=20, null=True, blank=True)
image = models.ImageField(verbose_name="头像", storage=OssStorage("user/portrait"), blank=True, null=True,
max_length=2048)
create_time = models.DateTimeField(verbose_name="创建时间", default=datetime.now)
update_time = models.DateTimeField(verbose_name="更新时间", default=datetime.now)
is_active = models.BooleanField(verbose_name="是否有效", default=True, blank=True, null=True)
is_staff = models.BooleanField(verbose_name="是否是员工", default=False)
is_superuser = models.BooleanField(verbose_name="是否是超级管理员", default=False)
USERNAME_FIELD = "username"
objects = MyMgr()
@property
def image_url(self):
if self.image and hasattr(self.image, 'url'):
return self.image.url
return ''
class Meta:
verbose_name = "用户"
verbose_name_plural = verbose_name
db_table = 'users'
def clean(self):
super().clean()
def __str__(self):
return "{}({})".format(self.username, self.nickname)
def get_short_name(self):
"""Return the short name for the user."""
return self.nickname
def getImage(self):
return format_html('<img src="{}" style="width:50px;height:auto">', self.image_url)
getImage.short_description = "头像显示"
分析代码,会发现,我们除了定义相关字段外,还进行了字段指定行为和一个对象指定行为:
USERNAME_FIELD = "username"
objects = MyMgr()
其中USERNAME_FIELD = "username"表示该model类中用哪个字段表示用户名。这个字段指定行为是必要操作,用于Django验证用户名密码以及后台登录等操作。
objects = MyMgr()是一种对象指定行为,主要目的是自定义在使用django-admin创建用户的时候Django的处理逻辑,其中MyMgr是自定义的一个管理类,代码如下:
from django.contrib.auth.models import BaseUserManager
class MyMgr(BaseUserManager):
def create_user(self, username, password=None):
"""
Creates and saves a User with the given email, date of
birth and password.
"""
if not username:
raise ValueError('Users must have an username')
user = self.model(
username=username,
)
user.set_password(password)
user.is_active = True
user.is_staff = True
user.save(using=self._db)
return user
def create_superuser(self, username, password=None):
"""
Creates and saves a superuser with the given email, date of
birth and password.
"""
user = self.create_user(
username,
password=password,
)
user.is_admin = True
user.is_staff = True
user.is_superuser = True
user.save(using=self._db)
return user
这个管理类中重载了两个函数,分别是create_user和create_superuser,分别定义了创建普通用户以及超级管理员用户的情况,在使用django-admin创建用户时将触发其中的业务逻辑,具体的实现逻辑根据各自定义的字段实现,这里就不阐述了。
在自定义Users中,还涉及到了诸如Image字段以及自定义Stroge等操作,这里不作讲解,大家可以看另一篇教程Django自定义Storage实现图片上传至各大OSS(上篇)_django-storages-优快云博客
最后,同样在settings里指定默认的用户类:
AUTH_USER_MODEL = "users.User"
然后在django shell中执行migrate操作:
django-admin makemigrations users
django-admin migrate users
至此,第二种自定义user类的方法也大功告成。相对于第一种自定义方法,第二种复杂了许多,但是第二种可以实现更加自由地进行用户类的自定义。
此外,我们还可以对Django中用户密码的生成和验证逻辑进行自定义,实现使用自定义算法的用户密码生成与验证,具体请看《Django自定义用户密码的生成与验证》
注册功能:
#form.py
from django.forms import Form
from django.forms import fields
from django.core.exceptions import ValidationError
class RegisterForm(Form):
username = fields.CharField(
required=True,
min_length=3,
max_length=18,
error_messages={
"required":"用户名不可以为空!",
"min_length":"用户名不能低于3位!",
"max_length":"用户名不能超过18位!"
}
)
password1 = fields.CharField(
required=True,
min_length=3,
max_length=18,
error_messages={
"required":"密码不可以空",
"min_length": "密码不能低于3位!",
"max_length": "密码不能超过18位!"
}
)
password2 = fields.CharField(required=False)
email = fields.EmailField(
required=True,
error_messages={
"required":"邮箱不可以为空!"
},
)
def clean_password2(self):
if not self.errors.get("password1"):
if self.cleaned_data["password2"] != self.cleaned_data["password1"]:
raise ValidationError("您输入的密码不一致,请重新输入!")
return self.cleaned_data
# view.py
from django.shortcuts import render
from django.contrib.auth import get_user_model
from .forms import *
from django.http import JsonResponse
User = get_user_model() # 获取User模型
def register(request):
if request.method == "GET":
return render(request,"register.html")
else:
form = RegisterForm(request.POST)
if form.is_valid():
username = form.cleaned_data["username"]
password = form.cleaned_data["password1"]
email = form.cleaned_data["email"]
username_exists = User.objects.filter(username=username).exists()
if username_exists:
return JsonResponse({"code":400,"message":"验证失败","data":{"username":"您输入的用户名已存在!","password1":"","password2":"","email":""}})
email_exists = User.objects.filter(email=email).exists()
if email_exists:
return JsonResponse({"code": 400, "message":"验证失败","data":{"username": "","password1":"","password2":"", "email": "您输入的邮箱已存在!"}})
User.objects.create_user(username=username,password=password,email=email)
return JsonResponse({"code": 200,"message":"验证通过", "data":{"username": "","password1":"","password2":"", "email": ""}})
else:
return JsonResponse({"code":400,"message":"验证失败","data":{"username":form.errors.get("username"),"password1":form.errors.get("password1"),"password2":form.errors.get("password2"),"email":form.errors.get("email")}})
登录功能
#form.py
from django.forms import Form
from django.forms import fields
class LoginForm(Form):
username = fields.CharField(
required=True,
min_length=3,
max_length=18,
error_messages={
"required":"用户名不可以为空!",
"min_length":"用户名不能低于3位!",
"max_length":"用户名不能超过18位!"
}
)
password = fields.CharField(
required=True,
error_messages={
"required":"密码不可以空",
}
)
#view.py
from django.shortcuts import render
from .forms import *
from django.http import JsonResponse
from django.contrib.auth import authenticate
from django.contrib.auth import login
# 登录视图名称不能起成login,与自带login函数重名
def loginView(request):
if request.method == "GET":
return render(request,"login.html")
else:
form = LoginForm(request.POST)
if form.is_valid():
username = form.cleaned_data.get("username")
password = form.cleaned_data.get("password")
remember = int(request.POST.get("remember"))
user = authenticate(request,username=username,password=password) # 使用authenticate进行登录验证,验证成功会返回一个user对象,失败则返回None
# 使用authenticate验证时如果is_active为False也会返回None,导致无法判断激活状态,
# 此时可以在seetings中配置:
# AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.AllowAllUsersModelBackend']
if user and user.is_active: # 如果验证成功且用户已激活执行下面代码
login(request,user) # 使用自带的login函数进行登录,会自动添加session信息
request.session["username"] = username # 自定义session,login函数添加的session不满足时可以增加自定义的session信息。
if remember:
request.session.set_expiry(None) # 设置session过期时间,None表示使用系统默认的过期时间
else:
request.session.set_expiry(0) # 0代表关闭浏览器session失效
return JsonResponse({"code": 200,"message":"验证通过","data":{ "error":""}})
elif user and not user.is_active:
return JsonResponse({"code": 400, "message": "用户未激活", "data": {"error": "该用户还没有激活,请<a href='#'>激活</a>"}})
else:
return JsonResponse({"code": 400, "message": "验证失败", "data": {"error": "用户名或密码错误"}})
else:
return JsonResponse({"code":400,"message":"用户名或密码格式错误","data":{"error":"用户名或密码错误"}})
退出功能
from django.contrib.auth import logout
from django.shortcuts import redirect
# 视图名不能起成logout
def logoutView(request):
logout(request) # 调用django自带退出功能,会帮助我们删除相关session
return redirect(request.META["HTTP_REFERER"])
此时我们就完成了通过自定义User模型实现注册登录以及退出一系列的功能,配合前端页面就可以完美实现了。
用户与权限管理
定义了用户模型,就不可避免的涉及到了用户权限问题,Django同样内置了Permission系统,该权限系统都是针对表或者模型级别的,比如某个模型上的数据可以进行增删改查,他不能够针对数据级别,比如某个表中的某条数据是否能够进行增删改查操作(如果要实现数据级别的,可以考虑使用django-guardian)。创建完一个模型后,针对该模型默认有增、删、改、查四种权限,权限存储了数据库中的auth_permission表中:

codename表示权限的名字,name表示权限的作用,content_type_id表示某张表的id,即用来绑定数据表的,他关联django_content_type这张表:

添加权限的两种方法:
#通过定义模型来添加权限:
class Address(models.Model):
"""
收货地址
"""
recv_address = models.TextField(verbose_name = "收货地址")
receiver = models.CharField(max_length=32,verbose_name="接收人")
recv_phone = models.CharField(max_length=32,verbose_name="收件人电话")
post_number = models.CharField(max_length=32,verbose_name="邮编")
buyer_id = models.ForeignKey(to=User,on_delete = models.CASCADE,verbose_name = "用户id")
class Meta:
permissions = (
("view_addresses", "查看地址"),
)
# 通过代码添加权限
from django.contrib.auth.models import Permission,ContentType
from .models import Address
content_type = ContentType.objects.get_for_model(Address)
permission = Permission.objects.create(name = "查看地址",codename = "view_addresses",content_type = content_type)

User模型和权限之间可以通过以下几种方式来进行管理:
1、user.user_permissions.set(permission_list):直接给定一个权限的列表。
2、user.user_permissions.add(permission,permission,...):一个个添加权限。
3、user.user_permissions.remover(permission,permission):一个个删除权限。
4、user.user_permissions.clear():清除权限
5、user.has_perm('<app_name>.<codename>'):判断是否拥有某个权限,权限参数是一个字符串,格式是app_name.codename。
6、user.get_all_permission():获得所有权限。
我们可以通过appname_user_user_permission这张表来查看某个用户是否拥有某项权限:

权限限定装饰器:
使用django.contrib.auth.decorators.permission_required可以很方便的检查某个用户是否拥有某项权限:
from django.contrib.auth.decorators import permission_required
@permission_required('Cart.view_cart',login_url="/login/",raise_exception=True)
# 第一个参数代表权限,login_url表示没有登录的话需要跳转的页面,raise_exception表示没有权限是返回403错误,默认是False,会跳转到login_url指定的页面。
def view_cart(request):
reture HttpResponse("您可以查看购物车")
分组
权限有很多,一个模型就最少有四个权限,如果一些用户拥有相同的权限,每次都需要重复添加是很不方便的,此时分组功能就可以帮我们解决这个问题,我们可以把一些权限归类,然后添加到某个分组中,之后再把和需要赋予这些权限的用户添加的这个分组中,就比较方便的进行管理了。分组我们使用的是django.contrib.auth.models.Group模型,每个用户组拥有id和name两个字段该模型在数据库被映射为auth_group数据表。
分组操作:
1、Group.objects.create(group_name):创建分组
2、group.permission:某个分组上的权限。多堆多的关系。
- group.permission.add:添加权限。
- group.permission.remove:移除权限。
- group.permission.clear:清除所有权限。
- user.get_group_permission():获取用户所属组的权限。
3、user.groups:某个用户上的所有分组。多对多的关系。
在模板中使用权限:
在settings.TEMPLATES.OPTIONS.context_processors下,因为添加了django.contrib.auth.context_processors.auth上下文处理器,因此可以在模板中直接通过 perms来获取用户的所有权限:
{% if perms.View.view_cart %}
购物车详情
{% endif %}
..........
部分内容转载:
(4 封私信) Django用户模型扩展终极指南:四种方法详细拆解 - 知乎
Django自定义用户类User Model的两种方式_django user model-优快云博客
Django自定义User模型以及用户系统(用户、权限、组)的使用_django 用户建立的模型 组权限不起 但不起作用-优快云博客
621

被折叠的 条评论
为什么被折叠?



