## django模型层详解 内容概要 - 测试环境配置 - ORM常用关键字(重要) - ORM执行SQL语句 - 神奇的双下线查询 - ORM外键字段的创建 - 外键字段数据的增删改查 - 多表查询(基于对象的跨表查询、基于双下划线的跨表查询) - 聚合查询 - 分组查询 - F与Q查询 1. 自带的sqlate3数据库对时间字段不敏感 有时候会展示错乱所以我们习惯切换成常见的数据库比如MYSQL django orm并不会帮我们创建数据库 所以需要我们自己提前创建好数据库 配置好settings文件数据库 ```python DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'djg07', 'HOST': 'localhost', 'PORT': 3306, 'USER': 'root', 'PASSWORD': 'root', 'CHARTSET': 'utf8' } } ``` 准备好表 ```python class User(models.Model): id = models.AutoField(primary_key=True, verbose_name='用户id') name = models.CharField(max_length=32, verbose_name='姓名') age = models.IntegerField(verbose_name='年龄') register_time = models.DateTimeField(verbose_name='注册时间', auto_now_add=True) # auto_now 是只要修改或添加就会把当前时间自动更新 # auto_now_add 是在增加第一次创建数据时自动把当前时间添加到里面 # models.DateTimeField年月日时分秒时间 models.DateField 年月日时间 def __str__(self): # 在打印类产生的方法时触发,只能返回字符串数据 return f'对象:{self.name}' ```  1. 单独测试django某个功能层 默认不允许单独测试某个py文件  2. 如果想要测试某个py文件(主要models.py) ```python #需要把这个放在最顶上 import os import django os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django07.settings') django.setup() #测试的数据一定要放在他们下面,否则会跟上面报错一样无法正常测试 from user import models models.User.objects.create(name='张三', age=18) ```  3. django orm底层还是SQL语句我们是可以查看的 如果我们手上是一个QuerySet对象 那么可以直接点query查看SQL语句  但是有的orm提供的方法不是一个QuerySet对象,但是又想看SQL语句那么也可以在配置文件中添加日志记录的`settings.py`配置 ```python LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'console':{ 'level':'DEBUG', 'class':'logging.StreamHandler', }, }, 'loggers': { 'django.db.backends': { 'handlers': ['console'], 'propagate': True, 'level':'DEBUG', }, } } ```  ## ORM常用关键字(方法) 1. 创建数据create() 创建数据并直接获取当前创建的数据对象 ```python res = models.User.objects.create(name='张三', age=18) print(res) res = models.User.objects.create(name='李四', age=28) print(res) res = models.User.objects.create(name='jason', age=18) print(res) res = models.User.objects.create(name='tom', age=38) print(res) res = models.User.objects.create(name='jerry', age=88) print(res) res = models.User.objects.create(name='ikun', age=58) print(res) ```  2. 筛选数据filter() 根据条件筛选数据 结果是QuerySet[对象1,对象2,···] ```python res = models.User.objects.filter() # 不写默认就算查所有字段 print(res) res = models.User.objects.filter(name='jason') # 写了就根据字段名与字段值查找数据 print(res) res = models.User.objects.filter(name='jason',age=18) # 可以写多个筛选条件,默认是and连接 print(res) ```  3. first() last() QuerySet支持索引取值但是只能只正数 并且ORM不建议我们使用索引 ```python res = models.User.objects.filter() print(res[0]) res = models.User.objects.filter(id=100) print(res[0]) ```  ```python # 无法执行 res = models.User.objects.filter() print(res[-1]) assert ((not isinstance(k, slice) and (k >= 0)) or AssertionError: Negative indexing is not supported. ```  使用first,与last去索引 ```python res = models.User.objects.filter() print(res.first()) print(res.last()) res = models.User.objects.filter(id=1000) print(res.first()) # 数据不存在索引取值也不会报错 print(res.last()) ```  4. 更新数据update (批量更新数据) ```python models.User.objects.update() # 批量更新数据 res = models.User.objects.filter(id=1).update(name='托马斯') # 手动筛选数据,在更新达到指定更新的效果 print(res) # update返回的是影响行数 ```  5. 删除数据delete (批量删除) ```python # models.User.objects.delete() # 批量删除数据 res = models.User.objects.filter(id=1).delete() # 手动筛选数据,进行删,我们一般都不会进行删除数据只会修改数据字段已达到删除的目的 print(res) ```  6. 查询所有数据all 结果是QuerySet[对象1,对象2] ```python res = models.User.objects.all() # 查询所有数据django默认查询全部的时候,查21条,需要了在继续查 print(res) ```  7. 获取字段数据values 结果是QuerySet[{},{},{}] ```python res = models.User.objects.values() # 获取所有数据字段所有值 print(res) print(res.first().get('name')) # 这里又重新查了一般数据库 """ (0.000) SELECT `user_user`.`id`, `user_user`.`name`, `user_user`.`age`, `user_user`.`register_time` FROM `user_user` ORDER BY `user_user`.`id` ASC LIMIT 1; args=() """ res = models.User.objects.all().values('name') # 获取全部数据name字段 print(res) res = models.User.objects.filter(id=4).values('name') # 过滤数据后在获取name字段数据 print(res) ```  8. values_list 根据指定字段获取数据结果QuerySet[(),(),()] ```python res = models.User.objects.filter(id=3).values_list('name', 'age') print(res) ```  9. 去重distinct 数据一模一样才可以去重如果有主键肯定不行 ```python res = models.User.objects.values('name', 'age').distinct() print(res) """ """ res = models.User.objects.values('name', 'age').distinct('age') print(res) ```  10. 排序oredr_by() ```python res = models.User.objects.all().order_by('age','id') # 默认为升序 print(res) res = models.User.objects.all().order_by('-age','id') # 字段前面加-号就是降序 print(res) ```  11. get() 根据条件筛选数据并直接获取到对象一旦条件不存在会直接报错 不建议使用 ```python res = models.User.objects.get(pk=2) # 有数据能查到,执行 print(res) res = models.User.objects.get(pk=1) # 没有数据查新报错 print(res) ```  12. 取反操作exclude() 是除了当前指定筛选数据不查询,查询其他的 ```python res = models.User.objects.exclude(pk=2) # 是除了当前指定筛选数据不查询,查询其他的 print(res) res = models.User.objects.all() print(res) ```  13. reverse() 颠倒顺序(被操作的对象必须是已经排过序的才可以) ```python res = models.User.objects.all().order_by('id').reverse() # 颠倒排序 print(res) res = models.User.objects.all().order_by('id') # 升序 print(res) res = models.User.objects.all().reverse() # 没有排序颠倒不生效 print(res) ```  14. count() 统计结果中数据的个数 ```python res = models.User.objects.filter(age=18).count() # 返回值为统计个数 print(res) ```  15. exists() 判断结果集中是否含有数据 如果有则返回Ture 没有则返回False ```python res = models.User.objects.all().exists() print(res) res = models.User.objects.filter(pk=100).exists() print(res) ```  ## ORM执行SQL语句 有时候ORM操作效率可能片偏低我们可以自己编写SQL语句 方式1:raw()只能查数据 ```python res = models.User.objects.raw('select * from user_user;') print(res) # 返回一个RawQuerySet对象 print(list(res)) # 拿出结果对象 print(res[0]) # 索引取值 # raw()无法执行增删改操作 # res = models.User.objects.raw('insert into user_user(name,age) values("1",2);') # res = models.User.objects.raw('delete from user_user where id=8') # res = models.User.objects.raw('update user_user set name="哈哈哈" where id=8') # print(res) ```  方式2 ```python from django.db import connection import time ctime = time.strftime("%Y-%m-%d %X") curosrs = connection.cursor() curosrs.execute('select * from user_user') print(curosrs.fetchall()) # curosrs.execute('insert into user_user(name,age,register_time) value ("李李",50,%s)', (ctime,)) curosrs.execute('delete from user_user where id=9') curosrs.execute('select * from user_user') print(curosrs.fetchall()) #添加修改添加以及删除数据无需二次确认 ```   ## 神奇的双下划线查询 只要还是Query Set对象就可以无限制的点Query Set对象的方法 返回是Query Set对象的ORM常用方法 ```python filter() # 筛选数据 all() # 查询所有数据 reverse() # 颠倒,必须排序之后才能使用否则不生效 order_by() # 对Query Set里面对象按照指定字段排序 exclude() # 查询除了当前数据的其他数据 values() # 查询所有数据的指定字段的值,为列表套字典 values_list() # 查询所有数据的指定字段的值,为列表套元组 distinct() # 去重数据必须一模一样,如果带主键那么数据肯定不一样,无法去重 ``` `__gt`大于 ```python # 查询年龄大于18的用户数据 res = models.User.objects.filter(age__gt=18) # gt大于 print(res) ```  `__lt`小于 ```python # 查询年龄小于38的用户数据 res = models.User.objects.filter(age__lt=38) print(res) ```  `__gte`大于等于,`__lte`小于等于 ```python # 查询年龄大于等于18的用户数据 # 查询年龄小于等于18的用户数据 res = models.User.objects.filter(age__gte=18) print(res) res= models.User.objects.filter(age__lte=38) print(res) ```  `__in`在这个数据集里面 ```python # 查询年龄是18或者28或者38的数据 res = models.User.objects.filter(age__in=(18,28,38)) print(res) ```  `__range`查询范围内的数据 ```python res = models.User.objects.filter(age__range=(18,38)) print(res) ```  `__contains`匹配字符区分大小写 `__icontains `匹配字符不区分大小写 ```python res = models.User.objects.filter(name__contains='j') print(res) # 区分大小写 res = models.User.objects.filter(name__icontains='J') print(res) # 不区分大小写 ```  `__year`,`__month`··· 根据时间查 不修改失去,根据日期查询,无法查询到数据 ```python res = models.User.objects.filter(register_time__year=2000) print(res) res = models.User.objects.filter(register_time__day=6) print(res) ```  `settings.py`配置文件修改 ```python TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True USE_TZ = False ``` 修改完毕之后正常查询数据 ```python res = models.User.objects.filter(register_time__year=2000) print(res) res = models.User.objects.filter(register_time__day=6) print(res) res = models.User.objects.filter(register_time__month=6) print(res) ```  ## ORM外键字段的创建 复习MySql外键关系 一对多:外键建在多的一方 多对多:外键需要建在第三张关系表中 一对一:建在任何一方都可以,但是推荐外键建在查询频率较高的一方 外键关系的判断:使用换位思考原则 ### 准备数据 1. 创建基础表(书籍表,出版社表,作者表,作者详情表) 2. 确定外键关系 一对多 ORM与MySql一致建在多的一方 多对多 ORM比MySQL有更多变化 1. 外键字段可以直接建在某张表中(查询频率较高的) 内部会自动帮我们创建第三张表 2. 自己创建第三表关系并创建外键字段 暂时忽略 一对一 ORM与MySQL一致 外键建在查询频率较高的一方 3. 在djngo1.x版中默认是级联更新级联删除的,而django2.x以上版默认不是级联更新级联删除的,而如果我们不加的话默认是会报错的  在多对多外键字段中不能添加级联更新级联删除  4. ORM创建 针对一对多和一对一与同步到表中之后会字段会自动加_id的后缀。 ```python 一对多 # 书籍表与出版社表外键关系 # 一本书只能对应一个出版社,而一个出版社可以对应很多本书书, # 所以书籍表对出版社表是多对一的关系,建在多的一方,也就是书籍表 publish = models.ForeignKey(to='Publsh', on_delete=models.CASCADE) # 在djngo1.x版中默认是级联更新级联删除的, # 而django2.x以上版默认不是级联更新级联删除的,而如果我们不加的话默认是会报错的 一对一 # 作者表与作者详情表外键关系 # 一个作者只能有一个详细信息,而一个详细信息只能有个作者所以是一对一的关系 # 又因为作者表查询频率比较高,所以外键字段建在查询频率较高的一方也就是作者表 authorDetail = models.OneToOneField(to='AuthorDetail', on_delete=models.CASCADE) ``` 针对多对对 不会在表中有展示 而是创建第三张表 ```python 多对多 # 书籍表与作者表外键关系 # 一本书可以有多个作者,而一个作者可以写多本书 # 所以书籍表与作者表的关系就是多对多, # 然后,数据表查询频率比作者表查询频率高,所以在ORM中外键字段建在书籍表中 author = models.ManyToManyField(to='Author') ```  ## 外键字段相关操作 ### 在没有外键表中插入数据 ### 插入出版社数据 ```python odels.Publsh.objects.create(name='北方出版社',address='北方') models.Publsh.objects.create(name='南方出版社',address='南方') models.Publsh.objects.create(name='东方出版社',address='东方') models.Publsh.objects.create(name='西方出版社',address='西方') ```  ### 插入作者详情表数据 ```python models.AuthorDetail.objects.create(phone=18888888888, address='北京') models.AuthorDetail.objects.create(phone=17777777777, address='南京') models.AuthorDetail.objects.create(phone=16666666666, address='上海') models.AuthorDetail.objects.create(phone=18899999999, address='厦门') ``` ### 针对一对多 插入数据可以直接填写表中的实际字段 ```python # 针对一对多 插入数据可以直接填写表中的实际字段 book_obj = models.Book.objects.create(title='水浒传', price=888.11, publish_id=1) # 直接添加数据需要指定表中的字段,外键字段会自动加_id所以在添加的时候字段名字为publish_id=1 print(book_obj) book_obj = models.Book.objects.create(title='三国演义', price=666.88, publish_id=1) print(book_obj) book_obj = models.Book.objects.create(title='斗罗大陆', price=8888.88, publish_id=2) print(book_obj) book_obj = models.Book.objects.create(title='斗破苍穹', price=6666.66, publish_id=3) print(book_obj) book_obj = models.Book.objects.create(title='三体', price=555.89, publish_id=4) print(book_obj) # # 针对一对多 插入数据也可以填写表中的类中字段名 publish_obj = models.Publsh.objects.filter(pk=1).first() models.Book.objects.create(title='金瓶', price=99999.99, publish=publish_obj) ## 使用对象进行数据插入要使用类中的字段名进行添加 # 书籍对象:水浒传 # 书籍对象:三国演义 # 书籍对象:斗罗大陆 # 书籍对象:斗破苍穹 # 书籍对象:三体 # 书籍对象:金瓶 ``` ### 针对一对一与一对多一致,既可以传数字也可以传对象 ```python models.Author.objects.create(name='jason', age=18, authorDetail_id=1) # 直接添加数据需要指定表中的字段,外键字段会自动加_id所以在添加的时候字段名字为authorDetail_id=1 models.Author.objects.create(name='tom', age=28, authorDetail_id=2) models.Author.objects.create(name='张三', age=58, authorDetail_id=4) authordetail_obj = models.AuthorDetail.objects.filter(pk=3).first() models.Author.objects.create(name='kunkun', age=38, authorDetail=authordetail_obj) ## 使用对象进行数据插入要使用类中的字段名authorDetail进行添加 ``` ### 针对多对多关系绑定 ```python # 增加数据 book_obj = models.Book.objects.filter(pk=1).first() book_obj.authors.add(1) book_obj.authors.add(4,3) book_obj = models.Book.objects.filter(pk=2).first() author_obj1 = models.Author.objects.filter(pk=1).first() author_obj2 = models.Author.objects.filter(pk=3).first() book_obj.authors.add(author_obj1, author_obj2) # 修改数据 book_obj = models.Book.objects.filter(pk=4).first() book_obj.authors.set((1, 3)) book_obj = models.Book.objects.filter(pk=4).first() author_obj = models.Author.objects.filter(pk=3).first() book_obj.authors.set([author_obj, ]) # 删除数据 book_obj = models.Book.objects.filter(pk=3).first() # book_obj.authors.remove(3) # 与下面同理 author_obj = models.Author.objects.filter(pk=3).first() book_obj.authors.remove(author_obj) # 清空当前数据对象的关系 bokk_obj.authors.clear() add()/remove() 多个位置参数(数字,对象) set() 可迭代对象(元组 列表)数字 对象 clear() 清空当前数据对象的关系 ``` ## ORM跨表查询 复习MySql 跨表查询的思路 子查询 分步操作:将一条Sql语句当作另一条SQL语句查询条件 连表操作:先整合多张表之后基于单表查询即可 ```python inner join 内连接 基于两个表中共有的数据字段展示 left join 左连接 以左表为基准展示数据 right join 右连接 以右表为基准展示数据 ``` ### 正反向查询的概念(重要) 正向查询 由外键字段所在的表数据查询为关联表的表数据 `正向` 反向查询 没有外键字段的表数据查询关联的表数据 `反向` 正反向的核心就看外键字段在不在当前数据所在的表中 ### ORM跨表查询的口诀(重要) 正向查询按外键字段 反向查询按表名小写 ## 基于对象的跨表查询 1.查询主键为1的书籍对应的出版社名称(正向查询) ```python book_obj = models.Book.objects.filter(pk=1).first() print(book_obj.publish.name) ```  2.查询主键为4的书籍对应的作者姓名(正向查询) ```python book_obj = models.Book.objects.filter(pk=3).first() print(book_obj.authors) # User.Author.None 是多对多字段,看见这个不要慌,里正确步骤就只差一步, print(book_obj.authors.all()) #就把所以数据拿出来 ]> ```  3.查询jason的电话号码(正向查询) ```python # 先查出jason对象。因为外键字段在当前表,所是正向查询 author_obj = models.Author.objects.filter(name='jason').first() # 因为是一对一字段,用对象点外键字段可以直接获取数据 print(author_obj.authorDetail.phone) ```  4.查询北方出版社出版过的书籍(反向查询) ```python publish_obj = models.Publsh.objects.filter(name='北方出版社').first() # 外键字段不在当前表,所是反向查询 # 反向查询是对象点类名小写,出版社是一对多关系所以用boot_set print(publish_obj.book_set) print(publish_obj.book_set.all()) ```  5.查询jason写过的书籍(反向查询) ```python author_obj = models.Author.objects.filter(name='jason').first() print(author_obj.book_set) print(author_obj.book_set.all().values('title')) ```  6.查询电话号码是18888888888的作者姓名(反向查询) ```python authordetail = models.AuthorDetail.objects.filter(phone=18888888888).first() # 一对一 结果是精准的类名不用加_set print(authordetail.author.name) ```  ### 总结 如果用结果对象,无论是正向还是反向,如果站在当前对象表对应跨到的查询表,当前对象可能有多个结果数据的情况下那么需要使用all()方法来取数据不能直接点类表中的字段拿数据,否则就可以使用点类表中的字段形式获取数据 ## 基于双下划线的跨表查询 1.查询主键为1的书籍对应的出版社名称(正向查询) ```python # 1.查询主键为1的书籍对应的出版社名称 # 先查询到主键为1的书籍,正向查询,直接点values('外键字段__要查询的关联表中的字段','因为手中本来就是书籍表所以可以直接查询当前表中的字段') res = models.Book.objects.filter(pk=1).values('publish__name','title') print(res) ```  2.查询主键为1的书籍对应的作者姓名(正向查询) ```python res = models.Book.objects.filter(pk=1).values('authors__name','title') print(res) ```  3.查询jason的电话号码(正向查询) ```python res = models.Author.objects.filter(name='jason').values('authorDetail__phone','name') print(res) ```  4.查询北方出版社出版过的书籍(反向查询) ```python res = models.Publsh.objects.filter(name='北方出版社').values('book__title') print(res) ```  5.查询jason写过的书籍(反向查询) ```python res = models.Author.objects.filter(name='jason').values('book__title') print(res) ```  6.查询电话号码是18888888888的作者姓名(反向查询) ```python res = models.AuthorDetail.objects.filter(phone=18888888888).values('author__name') print(res) ```  ## 进阶查询 1.查询主键为1的书籍对应的出版社名称 ```python # 用后面的表对前面条件进行过滤,然后再直接拿后面表要拿的数据 res = models.Publsh.objects.filter(book__id=1).values('name') print(res) ```  2.查询主键为3的书籍对应的作者姓名 ```python res= models.Author.objects.filter(book__pk=3).values('name') print(res) ```  3.查询jason的电话号码 ```python res= models.AuthorDetail.objects.filter(author__name='jason').values('phone') print(res) ```  4.查询北方出版社出版过的书籍 ```python res= models.Book.objects.filter(publish__name='北方出版社').values('title') print(res) ```  5.查询jason写过的书籍 ```python res = models.Book.objects.filter(authors__name='jason').values('title') print(res) ```  6.查询电话号码是18888888888的作者姓名 ```python res = models.Author.objects.filter(authorDetail__phone=18888888888).values('name') print(res) ```  ## 补充(各种姿势查) 查询主键为3的书籍对应的作者的电话号码(第一张表为开头) ```python res = models.Book.objects.filter(pk=3).values('authors__authorDetail__phone') print(res) ```  查询主键为3的书籍对应的作者的电话号码(第二张表为开头) ```python res = models.Author.objects.filter(book__pk=3).values('authorDetail__phone') print(res) ```  查询主键为3的书籍对应的作者的电话号码(第三张表为开头) ```python #从哪个表开始从哪跨表,必须连续跳,不能隔表跳 res = models.AuthorDetail.objects.filter(author__book__pk=3).values('phone') print(res) ```  #### 补充总结 从哪个表开始从哪跨表,必须连续跳,不能隔表跳 ## 聚合查询 常见聚合函数 `max` 取最大值 `min` 取最小值 `count` 统计总数 `sum` 求和 `avg` 求平均值 再ORM中支持单独使用聚合函数 aggregate ```python # 要先到导入模块才能使用 from django.db.models import Max, Min, Sum, Count, Avg res = models.Book.objects.aggregate(最大价格=Max('price'), 最小主键=Min('pk'), total_price=Sum('price'), total_num=Count('pk'), 平均值=Avg('price')) print(res) ```  ## 分组查询 分组查询关键字`annotate` ```python from django.db.models import Max, Min, Sum, Count, Avg res = models.Publsh.objects.annotate(Count('name')) print(res) ```  修改数据的sql_mode把`only-full-group_by`去掉  修改后就能正常查询了  统计每一本书的作者个数 ```python # 首先以Book书为分组,外键再手中,是正向查询 # 然后再用Count统计外键字段authors人数 # 最后再用values拿出统计的人数 res = models.Book.objects.annotate(aut_num =Count('authors')).values('title','aut_num') print(res) ```  统计出每个出版社卖的最便宜的书的价格 ```python #只有对象才会查询才会用到_set res = models.Publsh.objects.annotate(price_num=Min('book__price')).values('name','price_num') print(res) ```  统计不止一个作者的图书 ```python # 首先根据书籍分组,统计各个图书作者个数, # 然后过滤作者大于1的图书 # 拿出图书名称 res = models.Book.objects.annotate(aut_num=Count('authors')).filter(aut_num__gt=1).values('title','aut_num') print(res) ```  查询每个作者出的书的总价格 ```python res = models.Author.objects.annotate(aut_num=Count('book__pk'), sum_num=Sum('book__price')).values('aut_num', 'sum_num','name') print(res) ```  ```python models.表名.objects.annotate() # 按照表分组 models.表名.objects.values('字段名').annotate() # 按照values括号内的指定的字段分组 #查询每个作者出的书的总价格按字段分组,查询 res = models.Author.objects.values('pk').annotate(aut_num=Count('book__pk'),sum_num=Sum('book__price')).values('aut_num','sum_num','name') print(res) ``` ## F与Q查询 1.查询库存数大于卖出数的书籍 ```python from django.db.models import F res = models.Book.objects.filter(kucun__gt=F('sale')) print(res) ```  2.将所有书的价格涨800 ```python from django.db.models import F res = models.Book.objects.update(price=F('price')+800) print(res) ```  3.将所有书的名称后面追加爆款 ```python from django.db.models import F, Value from django.db.models.functions import Concat res = models.Book.objects.update(title=Concat(F('title'), Value('爆款'))) print(res) ```  4.查询主键是1或者价格大于2000的书籍 ```python # 查询主键是1或者价格大于2000的书籍 from django.db.models import F, Q, Value # 与或非条件的时使用Q查询 from django.db.models.functions import Concat res = models.Book.objects.filter(Q(pk=1) | Q(price__gt=2000)) print(res) # 查询主键不是1或者价格不大于2000的书籍 # ~是not &是and |是or res = models.Book.objects.filter(~Q(pk=1) | ~Q(price__gt=2000)) print(res) ```  总结F查询与Q查询 F查询针对与,需要再原有基础上做数据更改而使用的 Q查询针对与,再过条件中需要使用`& and 与`,`| or 或`,`~ not 非` 使用的 `Concat` 是在`F查询`做`字符串拼接`的时候使用 Last modification:December 17th, 2022 at 03:46 pm © 允许规范转载 Support 如果觉得我的文章对你有用,请随意赞赏 ×Close Appreciate the author Sweeping payments
Comment here is closed