高级应用四

高级应用

一、Admin后台管理

1、概述

什么是admin?

admin是后台可视化界面管理系统 方便模型的操作与维护

2、配置

  • settings.py
    INSTALLED_APPS = [
    'django.contrib.admin',
    ]
    汉化
    LANGUAGE_CODE = 'zh-Hans'

    TIME_ZONE = 'Asia/Shanghai'

  • 创建用户进行访问
    python manage.py createsuperuser
    依次输入用户名 邮箱 密码

  • 路由访问
    127.0.0.1:8000/admin/

3、配置后台

班级与学生模型类

  • models.py
    from django.db import models

    创建班级模型

    class Grade(models.Model):
    gname = models.CharField(max_length=10, default='python36')
    gnum = models.SmallIntegerField(default=30)
    gboynum = models.SmallIntegerField(default=15)
    ggirlnum = models.SmallIntegerField(default=15)
    def str(self):
    return self.gname
    class Meta:
    db_table = 'grade'

    创建学生模型

    class Student(models.Model):
    sname = models.CharField(max_length=10, default='张三')
    sage = models.SmallIntegerField(default=18)
    ssex = models.BooleanField(default=True)
    # 一对多外键关系 从表随着主表而删除
    # sgrade = models.ForeignKey(Grade, on_delete=models.CASCADE)
    # sgrade = models.ForeignKey(Grade, on_delete=models.SET_NULL, null=True)
    # sgrade = models.ForeignKey(Grade, on_delete=models.PROTECT)
    sgrade = models.ForeignKey(Grade, on_delete=models.SET_DEFAULT, default=1)

    def str(self):
    return self.sname
    class Meta:
    db_table = 'student'

  • 设置为中文显示(字段在后台进行中文显示)
    class Grade(models.Model):
    ...
    ggirlnum = models.SmallIntegerField(default=15, verbose_name='女生人数')
    class Meta:
    db_table = 'grade'
    verbose_name = '班级'
    class Student(models.Model):
    ...

  • admin.py
    from django.contrib import admin
    from App.models import Grade, Student

    class StudentInfo(admin.StackedInline): # 竖着

    class StudentInfo(admin.TabularInline): # 横着
    model = Student
    extra = 2

    @admin.register(Grade)
    class GradeAdmin(admin.ModelAdmin):
    inlines = [StudentInfo]
    # 字段显示
    list_display = ['pk', 'gname', 'gnum', 'gboynum', 'ggirlnum']
    # 过滤字段
    list_filter = ['gname']
    # 搜索字段
    search_fields = ['gname']
    # 分页
    list_per_page = 5 # 每页显示五条数据
    # 更改添加修改样式
    # fields = ['gname', 'gnum', 'gboynum', 'ggirlnum']
    # 添加修改分组(fieldsets和fields不能同时存在)
    fieldsets = [
    ('组一', {'fields':['gboynum', 'ggirlnum']}),
    ('组二', {'fields':['gname', 'gnum']})
    ]

    @admin.register(Student)
    class StudentAdmin(admin.ModelAdmin):
    def show_sex(self):
    if self.ssex:
    return '男'
    else:
    return '女'

    show_sex.short_description = '性别'
    list_display = ['pk', 'sname', 'sage', show_sex, 'sgrade']

    Register your models here.

    admin.site.register(Grade, GradeAdmin)

    admin.site.register(Student, StudentAdmin)

二、邮件发送

https://docs.djangoproject.com/zh-hans/3.2/topics/email/#django.core.mail.send_mass_mail

1、配置

基础阶段里面的邮箱配置

EMAIL_HOST_USER = ''
EMAIL_HOST = 'smtp.163.com'
EMAIL_HOST_PASSWORD = ''

2、发送邮件

def test_send_mail(req):
from django.core.mail import send_mail
send_mail(
'主体',
'邮件内容',
settings.EMAIL_HOST_USER,
['793390457@qq.com'],
fail_silently=False,
)
return HttpResponse('发送邮件')

3、群发

from django.core.mail import send_mass_mail
message1 = ('Subject here', 'Here is the message', 'from@example.com', ['first@example.com', 'other@example.com'])
message2 = ('Another Subject', 'Here is another message', 'from@example.com', ['second@test.com'])
send_mass_mail((message1, message2), fail_silently=False)

send_mass_mail()和send_mail()之间的主要区别在于,send_mail()每次执行时都会打开到邮件服务器的连接,而send_mass_mail()对其所有邮件使用单个连接。这使得send_mass_mail()的效率略微提高。

4、使用文本和html发送

from django.core.mail import EmailMultiAlternatives

subject, from_email, to = 'hello', 'from@example.com', 'to@example.com'
text_content = 'This is an important message.'
html_content = '

This is an important message.

'
msg = EmailMultiAlternatives(subject, text_content, from_email, [to])
msg.attach_alternative(html_content, "text/html")
msg.send()

5、使用html发送

from django.core.mail import send_mass_mail, EmailMessage
msg = EmailMessage('主体', '邮件内容', settings.EMAIL_HOST_USER, ['793390457@qq.com','948807313@qq.com'])
msg.content_subtype = "html" # Main content is now text/html
msg.send()

三、分页

1、Paginator对象

  • 创建对象
    Paginator()
  • 属性
    count 对象总数
    num_pages 页面总数
    page_range 页码列表
  • 方法
    page(num)

2、page对象

通过Paginator的方法page 得到page对象

  • 属性
    object_list 当前页上所有数据
    number 当前页的页码值
    paginator 获取paginator 对象
  • 方法
    has_next 是否有下一页
    has_previous 是否有上一页
    next_page_num 返回下一页页码
    previous_page_num 返回上一页页码

3、实例

  • views.py
    def index(req):
    nowPage = req.GET.get('page',1)
    stu = Student.objects.all()
    p = Paginator(stu, 2)
    # print(p)

    通过页码获取当前页的对象

    try:
    # 判断页码是否合法
    if int(nowPage) >= int(p.num_pages):
    nowPage = p.num_pages
    page = p.page(nowPage)
    except:
    page = p.page(p.num_pages)

    return render(req, 'index.html', {'page': page})

  • index.html




    Title



    首页


      {% for s in page.object_list %}
    1. {{ s.sname }}

    2. {% endfor %}



四、中间件

1、说明

中间件就是在请求与响应过程中 进行安全验证的功能 每个中间件组件都会负责做一些特定的功能

2、本质

python类

3、使用场景

  • 白名单、黑名单
  • url访问过滤
  • 缓存

浏览器->中间件->url路由地址分发->中间件->视图函数->中间件->响应->

4、方法

  • process_request(self, req)
    在执行视图之前被调用(分配url匹配视图) 每个请求都会被调用 返回None或者response对象
  • process_view(self, req, view_func, view_args, view_kwargs)
    调用视图之前进行调用 每个表请求都会调用 返回None或者response对象
  • process_response(self, req, response)
    所有响应在返回浏览器之前都会被调用 返回response
  • process_exception(self, req, exception)
    当视图抛出异常时进行调用

5、自定义中间件

  • App同级创建middleware文件夹
    App/
    middleware/
    myMiddly.py

  • myMiddly.py中创建MyMiddle类
    from django.utils.deprecation import MiddlewareMixin
    from django.shortcuts import HttpResponse, redirect

    class MyMiddle(MiddlewareMixin):
    def process_request(self, req):
    print(req.path)
    print(req.method)
    print(req.META['REMOTE_ADDR'])
    # if req.META['REMOTE_ADDR'] == '127.0.0.1':
    # return HttpResponse('您被拦截了')

    def process_exception(self, req, exception):
    print(exception)
    return redirect('/a/')

  • settings.py
    middleware中添加
    'middleware.myMiddle.MyMiddle',

五、用户认证

1、说明

auth_user 是用来维护用户信息的关系模式

2、auth_user

CREATE TABLE auth_user (
id int(11) NOT NULL AUTO_INCREMENT,
password varchar(128) NOT NULL,
last_login datetime(6) DEFAULT NULL,
is_superuser tinyint(1) NOT NULL,
username varchar(150) NOT NULL,
first_name varchar(150) NOT NULL,
last_name varchar(150) NOT NULL,
email varchar(254) NOT NULL,
is_staff tinyint(1) NOT NULL,
is_active tinyint(1) NOT NULL,
date_joined datetime(6) NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

3、用户模型属性方法

  • is_staff
    boolean 用户是否可以访问后台界面 默认False
  • is_active
    boolean 用户是否活跃 是否当前删除用户 默认True
  • is_authenticated()
    用户是否通过验证 (是否登录)
    request.user.is_authenticated()
  • make_password()
    密码加密 django自带的hash加密
  • check_password()
    验证密码是否正确
  • set_password()
    修改密码
  • authenticated()
    验证用户名和密码是否正确
    authenticated(username=username, password=password) 结果bool
  • create_user() 创建用户
    User.objects.create_user(username, email, password)
  • request.user
    获取当前用户登录对象
  • login_required
    设置当前视图只能登录后进行访问
  • login()
    处理登录
    login(req, user)
  • logout()
    退出登录
    logout(req)

4、注册

  • register.html



    注册页面



    {{ msg }}




    {% csrf_token %}




    *(为必填)




    (为必填)




    (为必填)




    *(为必填)










  • views.py
    # 注册
    def register(req):
    message = ''
    if req.method == 'POST':
    username = req.POST.get('username')
    email = req.POST.get('email')
    userpass = req.POST.get('userpass')
    u = User.objects.filter(username=username).exists()
    if not u:
    User.objects.create_user(username=username, email=email, password=userpass)
    return redirect('/login/')
    else:
    message = '该用户以存在'
    return render(req, 'register.html', {'msg': message})
    自己完善python正则验证用户名 密码 邮箱
    完善正则验证

5、登录

  • views.py
    from django.contrib.auth import authenticate, login

    登录

    def user_login(req):
    if req.method == 'POST':
    username = req.POST.get('username')
    userpass = req.POST.get('userpass')
    u = authenticate(username=username, password=userpass)
    if u:
    login(req, u)
    return redirect('/')
    else:
    return redirect('/login/')
    return render(req, 'login.html')

  • login.html



    登录页面





    {% csrf_token %}




    *(为必填)




    (为必填)

    {% comment %}


    (为必填)




    *(为必填)

    {% endcomment %}









  • index.html




    Title


    首页





    {% if request.user.is_authenticated %}
    欢迎:{{ request.user.username }}|退出登录
    {% else %}
    登录|注册
    {% endif %}



6、退出登录

  • views.py
    from django.contrib.auth import logout

    退出登录

    def user_logout(req):
    logout(req)
    return redirect('/')

7、修改密码

@login_required(login_url='/login/')
def update_password(req):
if req.method == 'POST':
old_userpass = req.POST.get('old_userpass')
new_userpass = req.POST.get('new_userpass')
username = req.user.username
u = authenticate(username=username, password=old_userpass)
if u:
# 修改密码
u.set_password(new_userpass)
u.save()
return redirect('/logout/')
else:
return redirect('/update_password/')
return render(req, 'update_password.html')

配置全局login_url

settings.py

LOGIN_URL = '/login/'

views.py

@login_required
def update_password(req):
return redirect('/update_password/')

8、配置优美前端页面

安装扩展库:

  • pip install django-bootstrap3

bootstrap官网:https://www.bootcss.com/

9、message组件(消息闪现)

  • 说明
    当某个动作处理完进行消息通知
  • 方法
    • message.debug
    • message.info
    • message.success
    • message.warning
    • message.error
  • 导入
    from django.contrib import messages
  • 使用
    def index(req):
    messages.success(req, 'index的消息')
    return render(req, 'index.html')

10、自定义用户表

  • 给auth_user表 添加字段

  • models.py
    from django.contrib.auth.models import AbstractUser
    from django.db import models
    # Create your models here.

    class User(AbstractUser):
    iphone = models.CharField(max_length=11, default='111111')
    icon = models.CharField(max_length=20, default='default.jpg')
    class Meta:
    db_table = 'user'

  • 配置 将模型类设置为自定义的模型
    AUTH_USER_MODEL = 'App.User'

  • 执行迁移
    python manage.py makemigrations
    python manage.py migrate

  • 添加自定义认证(可以使用用户名和手机号码都能登录)
    App下创建auth.py
    from django.contrib.auth.backends import ModelBackend
    from django.db.models import Q
    from App.models import User

    class MyBackend(ModelBackend):
    def authenticate(self, request, username=None, password=None, **kwargs):
    try:
    u = User.objects.get(Q(username=username)|Q(iphone=username)|Q(email=username))
    except:
    return None
    if u.check_password(password):
    return u

  • settings.py
    AUTHENTICATION_BACKENDS = ['App.auth.MyBackend']

  • views.py
    from App.models import User
    User.objects.create_user(username=username, email=email, password=userpass, iphone='15611833906')

六、文件上传

1、概述

文件上传到服务器端 存储在request.FILES中

2、注意

3、配置文件上传路径

  • 在settings.py
    STATICFILE_DIRS = [os.path.join(BASE_DIR, 'static')] # (前面模板中配置静态资源文件时就进行配置的)
    # 文件上传路径
    MEDIA_ROOT = os.path.join(BASE_DIR, r'App/static/upload')

4、文件上传的属性和方法

属性

  • file.name 文件名称
  • file.size 文件大小

方法

  • file.read() 读取文件上传的所有数据 当前方法适合于小文件
  • file.chunks() 按块返回文件 在循环迭代中 将大块文件拆分成小块写入到文件中
  • file.multiple_chunks()
    当前方法根据文件大小返回True或False 当fileSize大于2.5M 返回True 否则返回False 大小可调整

5、原始文件上传(单文件上传)

  • views.py
    # 原始单文件上传
    def upload_one(req):
    if req.method == 'POST':
    # 获取文件上传对象
    file = req.FILES.get('file')
    # print(file.name)
    # print(file.size)
    path = os.path.join(settings.MEDIA_ROOT, file.name)
    try:
    with open(path, 'wb') as f:
    if file.multiple_chunks():
    for ff in file.chunks():
    f.write(ff)
    else:
    f.write(file.read())
    messages.success(req, '文件上传成功!')
    except:
    messages.success(req, '文件上传失败!')

  • upload.html
    {% extends 'common/base.html' %}
    {% block title %}
    文件上传
    {% endblock %}
    {% block content %}

    文件上传



    {% csrf_token %}




6、完整上传

  • views.py
    # 文件类型限制
    def allowed_file(suffix):
    return suffix in settings.ALLOWED_FILES

    文件名称随机 uuid

    def random_name(suffix):
    u = str(uuid.uuid4())
    return u+'.'+suffix

    缩放 pillow

    def img_zoom(path, perfix='s_', w=200, h=150):
    img = Image.open(path)
    img2 = img.resize((w, h))
    # 拆分新路径 拼凑新名字
    fileList = os.path.split(path)
    new_path = os.path.join(fileList[0], perfix + fileList[1])
    img2.save(new_path)

    完整上传

    作业: 文件存储路径为/年/月/日/a.jpg

    def upload_one(req):
    if req.method == 'POST':
    # 获取文件上传对象
    file = req.FILES.get('file')
    # print(file.name)
    # print(file.size)
    # 后缀获取
    suffix = file.name.split('.')[-1]
    # 类型限定
    if not allowed_file(suffix):
    messages.error(req, '文件类型不允许')
    return redirect('/upload_one/')
    # 生成随机文件名
    name = random_name(suffix)
    # 拼凑地址
    path = os.path.join(settings.MEDIA_ROOT, name)
    try:
    with open(path, 'wb') as f:
    if file.multiple_chunks():
    for ff in file.chunks():
    f.write(ff)
    else:
    f.write(file.read())
    # 处理图片缩放
    img_zoom(path)
    img_zoom(path, 'm_', 400, 200)
    messages.success(req, '文件上传成功!')
    except:
    messages.success(req, '文件上传失败!')

    return render(req, 'own_center/upload.html')

7、多文件上传

  • views.py
    def upload_many(req):
    if req.method == 'POST':
    file_list = req.FILES.getlist('file')
    try:
    for file in file_list:
    path = os.path.join(settings.MEDIA_ROOT, file.name)
    with open(path, 'wb') as fw:
    if file.multiple_chunks():
    for ff in file.chunks():
    fw.write(ff)
    else:
    fw.write(file.read())
    messages.success(req, '文件上传成功!')
    except:
    messages.success(req, '文件上传失败!')
    return render(req, 'own_center/upload_many.html')

  • upload_many.html
    {% extends 'common/base.html' %}
    {% block title %}
    文件上传
    {% endblock %}
    {% block content %}

    多文件上传



    {% csrf_token %}




8、使用模型类进行上传

  • models.py
    不指定upload_to
    from django.contrib.auth.models import AbstractUser
    from django.db import models
    # Create your models here.

    class Upload(models.Model):
    # 不指定路径则为settings.py MEDIA_ROOT
    img = models.ImageField(max_length=200)
    file = models.FileField(max_length=200)
    class Meta:
    db_table = 'upload'
    指定upload_to
    class Upload(models.Model):
    # 上传到settings.py MEDIA_ROOT路径/年/月/日
    img = models.ImageField(max_length=200, upload_to='%Y/%m/%d/')
    file = models.FileField(max_length=200, upload_to='%Y/%m/%d/')
    class Meta:
    db_table = 'upload'

  • 注意
    如果使用了ImageField 需要安装pillow

  • views.py
    def upload_one(req):
    if req.method == 'POST':
    img = req.FILES.get('img')
    file = req.FILES.get('file')
    up = Upload(img=img, file=file)
    # up.img.name = random.randint(1232)
    up.save()
    print(up.img)
    print(up.img.name)
    print(up.img.url)
    messages.success(req, '上传成功')
    return render(req, 'own_center/upload.html')
    作业
    完成如果上传的是文件 保存到upload/file/y/m/d/中 如果是图片则保存到 upload/image/y/m/d/

  • file.content_type 获取文件上传类型

七、celery

1、问题

用户发起请求 request 等待服务端的response 响应 但是如果在服务端视图函数中 有一些耗时操作 那用户就要等待很长时间才能接收到响应 那么对于用户来说 体验很差

另外我们网站 比如要同步数据 需要用户请求才会进行触发 如果想隔一段时间进行处理 做不到的

2、解决

  • 将耗时操作放到celery中执行
  • 定时执行

3、celery模型模块包含

  • 任务task
    本质是一个python函数 也就是将耗时的操作 封装成函数
  • 队列
    将执行的任务放到队列中
  • 工人 worker
    负责执行队列中的任务
  • 代理 broker
    负责调度

4、安装模块

  • pip install celery
  • pip install celery-with-redis
  • pip install django-celery
  • pip install redis==2.10.6

5、配置

  • INSTALL_APPS中
    INSTALLED_APPS = [
    ...
    'App',
    'djcelery',
    ]
  • settings.py下方添加
    import djcelery
    djcelery.setup_loader() # 初始化
    BROKER_URL = 'redis://127.0.0.1:6379/0'
    CELERY_IMPORTS = ('App.task') # 导入执行的任务

6、添加耗时任务

App/task.py

from celery import task
import time

耗时操作

@task
def test1():
print('耗时开始')
time.sleep(5)
print('耗时结束')

7、执行迁移

生成所需celery表

python manage.py migrate

8、project下新建celery

from future import absolute_import

import os
from celery import Celery
from django.conf import settings

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'whthas_home.settings')

app = Celery('portal')

app.config_from_object('django.conf:settings')
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

@app.task(bind=True)
def debug_task(self):
print('Request: {0!r}'.format(self.request))

9、工程project/init

from .celery import app as celery_app

10、视图函数中使用celery

from django.shortcuts import render, HttpResponse
import time
from .task import test1
# Create your views here.
def index(req):
# 执行耗时任务
test1.delay()
return HttpResponse('index')

11、启动顺序

  • 启动redis
    redis-cli
  • 启动服务
    python manage.py runserver
  • 启动worker
    python manage.py celery worker --loglevel=info

12、将邮件发送发到celery中

task.py

from celery import task
import time
from django.conf import settings

耗时操作

@task
def test1(subject, content, *args):
# print('耗时开始')
# time.sleep(5)
# print('耗时结束')
from django.core.mail import send_mass_mail, EmailMessage
msg = EmailMessage(subject, content, settings.EMAIL_HOST_USER, args)
msg.content_subtype = "html" # Main content is now text/html
msg.send()

views.py

from django.shortcuts import render, HttpResponse
import time
from .task import test1
# Create your views here.
def index(req):
# 执行耗时任务
test1.delay('主题', '内容', '793390457@qq.com')
return HttpResponse('index')

13、定时执行

  • 定时执行一个任务
    task.py添加定时任务
    @task
    def test2():
    print('定时任务开启')
    print('定时任务结束')
    settings.py中添加如下代码
    from datetime import timedelta
    # 添加定时任务
    CELERYBEAT_SCHEDULE = {
    'schedule-test':{
    'task':'App.task.test2',
    'schedule': timedelta(seconds=3), # 定时任务3秒一次
    # 'args': (1,) # 如果任务需要传参则传递参数
    },
    }
  • 启动
    • 启动redis
      redis-cli
    • 启动django
      python manage.py runserver
    • 启动worker
      python manage.py celery worker --loglevel=info
    • 开启定时任务
      python manage.py celery beat --loglevel=info
  • 定时执行多个任务
    task.py
    @task
    def test3(i):
    print('定时任务开启')
    print(i)
    print('定时任务结束')
    settings.py
    from datetime import timedelta
    # 添加定时任务
    CELERYBEAT_SCHEDULE = {
    'schedule-test1':{
    'task':'App.task.test2',
    'schedule': timedelta(seconds=3), # 定时任务3秒一次
    # 'args': (1,) # 如果任务需要传参则传递参数
    },
    'schedule-test2':{
    'task':'App.task.test3',
    'schedule': timedelta(seconds=3), # 定时任务3秒一次
    # 'args': (1,) # 如果任务需要传参则传递参数
    },
    }

14、修改报错

  • 启动django
  • 启动worker

出现报错

按照报错位置 将所有async 改为async1

15、图片验证码

  • 安装
    pip install pillow
  • views.py
    def verifycode(request):
    # 引入绘图模块
    from PIL import Image, ImageDraw, ImageFont
    # 引入随机函数模块
    import random
    # 定义变量,用于画面的背景色、宽、高
    bgcolor = (random.randrange(20, 100), random.randrange(
    20, 100), random.randrange(20, 100))
    width = 100
    height = 50
    # 创建画面对象
    im = Image.new('RGB', (width, height), bgcolor)
    # 创建画笔对象
    draw = ImageDraw.Draw(im)
    # 调用画笔的point()函数绘制噪点
    for i in range(0, 100):
    xy = (random.randrange(0, width), random.randrange(0, height))
    fill = (random.randrange(0, 255), 255, random.randrange(0, 255))
    draw.point(xy, fill=fill)
    # 定义验证码的备选值
    str = '1234567890QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm'
    # 随机选取4个值作为验证码
    rand_str = ''
    for i in range(0, 4):
    rand_str += str[random.randrange(0, len(str))]
    # 构造字体对象
    font = ImageFont.truetype(r'/home/xlg/PycharmProjects/fonts/ADOBEARABIC-BOLDITALIC.OTF', 40)
    # 构造字体颜色
    fontcolor1 = (255, random.randrange(0, 255), random.randrange(0, 255))
    fontcolor2 = (255, random.randrange(0, 255), random.randrange(0, 255))
    fontcolor3 = (255, random.randrange(0, 255), random.randrange(0, 255))
    fontcolor4 = (255, random.randrange(0, 255), random.randrange(0, 255))
    # 绘制4个字
    draw.text((5, 2), rand_str[0], font=font, fill=fontcolor1)
    draw.text((25, 2), rand_str[1], font=font, fill=fontcolor2)
    draw.text((50, 2), rand_str[2], font=font, fill=fontcolor3)
    draw.text((75, 2), rand_str[3], font=font, fill=fontcolor4)
    # 释放画笔
    del draw
    # 存入session,用于做进一步验证
    request.session['verify'] = rand_str
    # 内存文件操作
    import io
    buf = io.BytesIO()
    # 将图片保存在内存中,文件类型为png
    im.save(buf, 'png')
    # 将内存中的图片数据返回给客户端,MIME类型为图片png
    return HttpResponse(buf.getvalue(), 'image/png')
  • 注意
    windwos
    如果使用的是windwos系统需要更改字体库文件位置
    windwos+r->fonts->出现字体库文件夹 选择一个字体文件就可以了
    linux
    将字体库文件放置一个位置 然后更改路径就可以了
  • 配置路由
    path('verifycode/', views.verifycode),
  • 配置模板




    Title




posted @ 2022-03-01 10:11  寻月隐君  阅读(58)  评论(0)    收藏  举报