学习日记之《Django3 Web应用开发实战》——第十一章——常用的web应用程序

第十一章——常用的web应用程序

会话的配置与操作

# Django 提供了5种session的保存方式
# 数据库方式保存  默认是这种方式 无需设置
SESSION_ENGINE = 'django.contrib.sessions.backends.db'

# 以文件的方式保存
SESSION_ENGINE = 'django.contrib.sessions.backends.file'
SESSION_FILE_PATH = '/文件路径'

# 以缓存的形式保存
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
# 设置缓存名,默认是内存缓存的方式,与缓存机制的设置有关
SESSION_CACHE_ALIAS = 'default'

# 数据库加缓存的形式保存
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'

# Cookie形式保存
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'

使用会话实现商品抢购

from django.shortcuts import render, redirect
from .models import Product
from django.contrib.auth.decorators import login_required

# Create your views here.

@login_required(login_url='/admin/login')
def index(request):
    id = request.GET.get('id', '')
    if id:
        idList = request.session.get('idList', [])  # 向session 中添加键值对
        if not id in idList:
            idList.append(id)
        request.session['idList'] = idList
        return redirect('purchase:index')
    products = Product.objects.all()
    return render(request, 'index.html', locals())


def orderView(request):
    idList = request.session.get('idList', [])  # 获取session中的键值对
    del_id = request.GET.get('id', '')
    if del_id in idList:
        idList.remove(del_id)
        request.session['idList'] = idList
    products = Product.objects.filter(id__in=idList)
    return render(request, 'order.html', locals())

缓存机制

为解决短时间内访问量过大的问题,可以实行缓存策略,若在某个时间内再次收到同一个请求,不再执行响应过程,而是从内存或者高速缓存系统中获取该请求的响应内容。

Django 提供5种缓存机制

1、Memcached 缓存

高性能的分布式内存对象缓存系统,需要安装Memcached系统服务器,DJango 通过python-memcached或者pylibme模块来调用Memcached系统服务器,适合超大型网站使用

# Memcached配置
# BACKEND用于配置缓存引擎,LOCATION是Memcached服务器的IP地址
# django.core.cache.backends.memcached.MemcachedCache
# python-memcached模块连接Memcached
# or
# django.core.cache.backends.memcached.PyLibMCCache
# 使用pylibme连接Memcached
CACHE = {
    'default':{
    'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
    # or
    # 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
    'LOCATION': [
        '172.19.26.240.11211',
        '172.19.26.240.11212',
    ]
    }
}

2、数据库缓存,适合大中型网站

# 数据库缓存配置
# BACKEND用于配置缓存引擎,LOCATION是数据表命名
CACHE = {
    'default': {
    'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
    'LOCATION': 'my_cache_table',
    }
}

3、文件系统缓存,适合中小型网站

# 文件系统缓存
# BACKEND用于配置缓存引擎,LOCATION是文件保存的绝对路径
CACHE = {
    'default': {
    'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
    'LOCATION': 'D:/django_cache',
    }
}

4、本地内存缓存, 适合开发测试

# 本地内存缓存
# BACKEND用于配置缓存引擎,LOCATION用于存储器命名,识别单个存储器
CACHE = {
    'default': {
    'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
    'LOCATION': 'unique_snow',
    }
}

5、虚拟缓存,适合开发测试

# 虚拟缓存缓存
# BACKEND用于配置缓存引擎
CACHE = {
    'default': {
    'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
    }
}

以数据库缓存方式举例

# 数据库缓存配置
# BACKEND用于配置缓存引擎,LOCATION是数据表命名
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
        'LOCATION': 'my_cache_table',
        # TIMEOUT 设置缓存的生命周期,秒为单位,若为None,则为永不过期
        'TIMEOUT': 60,
        'OPTION': {
            # MAX_ENTRIES 代表最大缓存记录的数量
            'MAX_ENTRIES': 100,
            # CULL_FREQUENCY 当缓存达到最大量的时候,剔除缓存的数量
            'CULL_FREQUENCY': 3,
        }
    },
    # 可设置多个缓存数据表
    'mydjango': {
            'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
            'LOCATION': 'mydjango_cache_table',
            # TIMEOUT 设置缓存的生命周期,秒为单位,若为None,则为永不过期
            'TIMEOUT': 60,
            'OPTION': {
                # MAX_ENTRIES 代表最大缓存记录的数量
                'MAX_ENTRIES': 100,
                # CULL_FREQUENCY 当缓存达到最大量的时候,剔除缓存的数量
                'CULL_FREQUENCY': 3,
            }
        },
}
# 创建缓存数据表,依赖于DATABASES数据库的配置,如果配置了多库的,缓存数据表默认在default数据库内
python manage.py createcachetable

全站缓存

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',  #
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    # 配置全站缓存
    'django.middleware.cache.FetchFromCacheMiddleware',
]

# 设置缓存生命周期
CACHE_MIDDLEWARE_SECONDS = 15
# 设置缓存存放的数据表
# 来自 setting中CACHES的配置
CACHE_MIDDLEWARE_ALIAS= 'default'
# 设置缓存字段表cache_key的值
# 同一个django项目 多个站点之间 共享缓存
CACHE_MIDDLEWARE_KEY_PREFIX= 'myview'

视图缓存

# timeout 缓存的生命周期
# cache setting中缓存存放的数据表,没有设定默认保存在 default中
# key_prefix 同一个django项目 多个站点之间 共享缓存
from django.views.decorators.cache import cache_page
@cache_page(timeout=10, cache='mydjango', key_prefix='myview')
def cacheview(request):
    return render(request, 'index2.html')

路由缓存

from django.urls import path
from . import views
from django.views.decorators.cache import cache_page

app_name = 'purchase'
urlpatterns = [  
    path('index2/', cache_page(timeout=10, cache='mydjango', key_prefix='myurl')(views.cacheview), name='index2')
]

模板缓存

模板缓存使用缓存标签实现

<html>
<body>
    <div>
    {# 设置模版缓存 #}
    {% load cache %}
    {# 10代表生命周期 #}
    {# MyTemp代表缓存数据的cache_key字段 #}
    {# using="MyDjango"代表缓存数据表 #}
    {% cache 10 MyTemp using="MyDjango" %}
        <div>Hello Django</div>
    {# 缓存结束 #}
    {% endcache %}
    </div>
</body>
</html>

消息框架

消息框架主要在视图和模板中使用。

# settings.py
# 设置消息框架功能引擎
# MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
# MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'
# 默认,无需设置
MESSAGE_STORAGE = 'django.contrib.messages.storage.fallback.FallbackStorage'
# views.py
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
from django.views.generic.edit import CreateView

# 视图中使用消息框架
def messageView(request):
    # 帅选并保留比message.WARNING级别更高的消息提示
    # set_level 必须在添加消息提示前使用,否则无法筛选
    # messages.set_level(request, messages.WARNING)
    # 消息添加方法一
    # messages.debug(request, 'DEBUG 类型')
    # messages.info(request, 'INFO 类型')
    # messages.success(request, 'SUCCESS 类型')
    # messages.warning(request, 'WARNING 类型')
    # messages.error(request, 'ERROR 类型')
    # 消息添加方法二
    messages.add_message(request, messages.INFO, 'INFO 类型2')
    # 自定义消息类型
    # request 代表参数request
    # 66 代表参数level
    # 自定义类型代表message
    # MyDefile 代表参数extra_tags
    # messages.add_message(request, 66, '自定义类型', 'MyDefine')
    # 获取所有消息提示的最低级别
    current_level = messages.get_level(request)
    print(current_level)
    # 获取当前用户的消息提示对象
    mg = messages.get_messages(request)
    print(mg)
    return render(request, 'messageview.html', locals())

# 视图类使用消息框架
def success(request):
    return render(request, 'messageview.html', locals())

# 消息框架定义的SuccessMessageMixin 只能用于数据操作视图,只能与视图类 FormVIew、CreateView、UpdateView和DeleteView结合使用
class IClass(SuccessMessageMixin, CreateView):
    model = Product
    fields = ['name', 'slogan', 'sell', 'price', 'photo']
    template_name = 'indexClass.html'
    success_url = '/purchase/success'
    success_message = 'created successfully'
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>消息提示</title>
</head>
<body>
    {% if messages %}
        <ul>
            {% for m in messages %}
                <script>alert('消息内容:');</script>    # 以弹窗的形式出现
                <li>消息内容:{{ m.message }}</li>
                <div>消息类型:{{ m.level }}</div>
                <div>消息级别:{{ m.level_tag }}</div>
                <div>参数extra_tags的值:{{ m.extra_tags }}</div>
                <div>extra_tags和level_tag组合值:{{ m.tags }}</div>
            {% endfor %}
        </ul>
    {% else %}
        <script>alert('暂无消息');</script>
    {% endif %}
</body>
</html>

分页功能

# views.py
from django.shortcuts import render
from django.core.paginator import Paginator
from django.core.paginator import EmptyPage
from django.core.paginator import PageNotAnInteger
from .models import Product


def index5(request, page):
    # 获取模型PersonInfo的全部数据
    person = Product.objects.all().order_by('-name')
    # 设置每一页的数据量为2
    p = Paginator(person, 2, 1)
    try:
        pages = p.get_page(page)
    except PageNotAnInteger:
        # 如果参数page的数据类型不是整型,就返回第一页数据
        pages = p.get_page(1)
    except EmptyPage:
        # 若用户访问的页数大于实际页数,则返回最后一页的数据
        pages = p.get_page(p.num_pages)
    return render(request, 'index5.html', locals())
<!DOCTYPE html>
<html lang="zh-hans">
<head>
    {% load static %}
    <title>分页功能</title>
    <link rel="stylesheet" href="{% static 'css/base.css' %}"/>
    <link rel="stylesheet" href="{% static 'css/lists.css' %}">
</head>
<body class="app-route model-hkrouteinfo change-list">
<div id="container">
    <div id="content" class="flex">
        <h1>分页功能</h1>
        <div id="content-main">
            <div class="module filtered" id="changelist">
                <form id="changelist-form" method="post">
                    <div class="results">
                        <table id="result_list">
                            <thead>
                            <tr>
                                <th class="action-checkbox-column">
                                    <div class="text">
                                        <span><input type="checkbox"/></span>
                                    </div>
                                </th>
                                <th><div class="text">姓名</div></th>
                                <th><div class="text">年龄</div></th>
                            </tr>
                            </thead>
                            <tbody>
                            {% for p in pages %}
                                <tr>
                                    <td class="action-checkbox">
                                        <input type="checkbox" class="action-select">
                                    </td>
                                    <td>{{ p.name }}</td>
                                    <td>{{ p.age }}</td>
                                </tr>
                            {% endfor %}
                            </tbody>
                        </table>
                    </div>
                    <p class="paginator">
                        {# 上一页的路由地址 #}
                        {% if pages.has_previous %}
                            <a href="{% url 'purchase:index5' pages.previous_page_number %}">上一页</a>
                        {% endif %}
                        {# 列出所有的路由地址 #}
                        {% for n in pages.paginator.page_range %}
                            {% if n == pages.number %}
                                <span class="this-page">{{ pages.number }}</span>
                            {% else %}
                                <a href="{% url 'purchase:index5' n %}">{{ n }}</a>
                            {% endif %}
                        {% endfor %}
                        {# 下一页的路由地址 #}
                        {% if pages.has_next %}
                            <a href="{% url 'purchase:index5' pages.next_page_number %}">下一页</a>
                        {% endif %}
                    </p>
                </form>
            </div>
        </div>
    </div>
</div>
</body>
</html>

国际化和本地化

(未成功,待再尝)

centos 系统下安装语言工具
yum install gettext gettext-devel

settings.py
LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True

# 配置国际化和本地化
# 国际化翻译文件路径

LOCALE_PATHS = [os.path.join(BASE_DIR, 'language')]
# 设置支持的语言
LANGUAGES = [
    ('en', 'English'),
    ('zh', 'Simplified Chinese'),
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',  #
    # 使用国际化和本地化功能
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    # 配置全站缓存
    'django.middleware.cache.FetchFromCacheMiddleware',
]
# views.py
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy

def index6(request):
    print('qq', request.LANGUAGE_CODE)
    if request.LANGUAGE_CODE == 'zh':
        language = gettext('Chinese')
        # language = gettext_lazy('Chinese')
    else:
        language = gettext('English')
        # language = gettext_lazy('English')
    print('ll', language)
    return render(request, 'index6.html', locals())
<!DOCTYPE html>
<html>
<head>
    {% load i18n %}
    <title>{% trans "Choice language" %}</title>
</head>
<body>
<div>
    {#将配置属性LANGUAGES进行列举,显示网站支持的语言#}
    {% get_available_languages as languages %}
    {% trans "Choice language" %}
    {% for lang_code, lang_name in languages %}
        {% language lang_code %}
            <a href="{% url 'purchase:index6'%}">{{ lang_name }}</a>
        {% endlanguage %}
    {% endfor %}
</div>
<br>
<div>
{% blocktrans %}
    The language is {{ language }}
{% endblocktrans %}
</div>
</body>
</html>
以管理员身份执行
python manage.py makemessages -l zh
python manage.py compilemessages

单元测试

from django.test import TestCase
from .models import PersonInfo
from django.contrib.auth.models import User
from django.test import Client
from .models import MyUser

# Create your tests here.
class PersonInfoTest(TestCase):
    # 添加数据
    def setUp(self):
        PersonInfo.objects.create(name='cooper', age=30)
        PersonInfo.objects.create(name='bob', age=18)

    # 编写测试用例
    def test_personinfo_age(self):
        # 编写用例
        name1 = PersonInfo.objects.get(name='cooper')
        name2 = PersonInfo.objects.get(name='bob')
        # 判断测试用例的执行结果
        self.assertEqual(name1.age, 30)
        self.assertEqual(name2.age, 18)

    # 编写测试用例
    def test_api(self):
        c = Client()
        response = c.get('/api/')
        self.assertIsInstance(response.json(), dict)

    def test_html(self):
        c = Client()
        response = c.get('/index7/')
        name = response.context['person'].name
        self.assertEqual(name, 'cooper')
        self.assertTemplateUsed(response, 'index7.html')


class UserTest(TestCase):
    # 添加数据
    @classmethod
    def setUpTestData(cls):
        MyUser.objects.create_user(username='test', password='test', email='testqq@.com',
                                   qq='123', wechat='123', mobile='123')

    def test_user(self):
        r = MyUser.objects.get(username='test')
        self.assertEquals(r.email, 'testqq@.com')
        self.assertTrue(r.password)

    def test_login(self):
        c = Client()
        r = c.login(username='test', password='test')
        self.assertTrue(r)


python manage.py test  # 执行整个项目的测试类
python manage.py test index  # 执行index 应用的测试类
python manage.py test index.tests.somemodle  # 执行应用下某个测试模型的测试类
python manage.py test  --pattern="tests.py"  # 执行整个项目带有tests开头的.py的文件

自定义中间件

# 创建自定义中间件
from django.utils.deprecation import MiddlewareMixin

class MyMiddleware(MiddlewareMixin):
    def __init__(self, get_response=None):
        """运行Django将会自动执行"""
        self.get_response = get_response
        # super().__init__()
        print("this is __init__")

    def process_request(self, request):
        '''完成请求对象的创建,但用户访问的网址尚未与网站路由地址匹配'''
        print("this is process_request")

    def process_view(self, request, func, *args, **kwargs):
        """完成用户访问的网址,与路由地址匹配,但尚未执行视图函数"""
        print("this is process_view")

    def process_exception(self, request, exception):
        """在执行视图函数的时候发生异常"""
        print("this is process_exception")

    def process_response(self, request, response):
        """完成视图函数的执行,但尚未将响应内容返回给浏览器"""
        print("this is process_response")
        print('rr', response)
        print('r1', type(response))
        return response
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',  #
    # 使用国际化和本地化功能
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    # 配置全站缓存
    'django.middleware.cache.FetchFromCacheMiddleware',
    'web1.myMiddleware.MyMiddleware',  # 自定义中间件
]

异步编程

在Django中启用ASGI(Asynchronous Server Gateway Interface),你需要做以下几步:

确保你的Django版本是3.1或更高。
在你的Django项目的settings.py文件中,找到ASGI_APPLICATION设置,并确保它指向你的Django应用的ASGI应用实例。

如果你的Django项目名为myproject,可能会看起来像这样
# settings.py

# ...

ASGI_APPLICATION = 'myproject.asgi.application'

# ...

创建一个asgi.py文件在你的Django项目根目录下。这个文件会导入并定义ASGI应用实例

# asgi.py

import os
from django.core.asgi import get_asgi_application

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

application = get_asgi_application()

配置你的服务器来使用ASGI。这通常涉及到选择一个ASGI服务器或者中间件,比如Uvicorn或Daphne。
例如,使用Uvicorn启动ASGI服务器的命令如下:

pip3 install uvicorn

在Django项目中使用uvicorn作为异步的ASGI服务器,你可以通过以下命令来指定IP和端口启动项目:
uvicorn your_project_name.asgi:application --host 0.0.0.0 --port 8000

uvicorn your_project_name.asgi:application --reload --host 0.0.0.0 --port 8000
--reload 参数会在每次代码更改后自动重启服务器,这对开发环境很有用,但不推荐在生产环境使用,因为它可能会影响性能。

同步任务和异步任务

应用下创建 tasks.py
import time
import asyncio
from asgiref.sync import sync_to_async
from .models import TaskInfo


# 异步任务
async def asyns():
    startTime = time.time()
    for num in range(1, 6):
        await asyncio.sleep(1)
        print('异步任务', num)
    await sync_to_async(TaskInfo.objects.create, thread_sensitive=True)(task='异步任务')
    print('异步任务完成 用时:', time.time() - startTime )


# 同步任务
def syns():
    startTime = time.time()
    for num in range(1, 6):
        time.sleep(1)
        print('同步任务', num)

    TaskInfo.objects.create(task='同步任务')
    print('同步任务完成,用时:', time.time() - startTime)

views.py
import asyncio
from .tasks import syns, asyns
from django.http import HttpResponse

# 同步视图调用同步任务
def synView(request):
    syns()
    return HttpResponse('Hello this is syns')


# 异步视图调用异步任务
async def asynView(request):
    loop = asyncio.get_event_loop()
    loop.create_task(asyns())
    return HttpResponse('Hello this is asyns!')

信号机制

创建一个信号文件 如signals.py
from django.core.signals import request_started  # HTTP 请求之间触发
import time
# 方法一
# 设置内置信号request_started 的回调函数signal_request
def signal_request(sender, **kwargs):
    time.sleep(10)
    print('request is coming.')
    print('sender', sender)
    print('kwargs', kwargs)

# 将内置信号request_started 和回调函数 signal_request绑定
request_started.connect(signal_request)


# 方法二
from django.dispatch import receiver
# 使用内置函数receiver作为回调函数的装饰器
# 将内置信号request_started 和回调函数signal_request_2绑定
@receiver(request_started)
def signal_request_2(sender, **kwargs):
    time.sleep(10)
    print('request_2 is coming.')
    print('sender_2', sender)
    print('kwargs_2', kwargs)
在视图中导入即可
from web1.signalsw import signal_request, signal_request_2

自定义信号

signals.py
from app1.models import TaskInfo
from django.dispatch import Signal

my_signals = Signal()
# 注册信号的回调函数
def mySignal(sender, **kwargs):
    print('sender is ', sender)
    print('kwargs is ', kwargs)
    print('TaskInfos ', TaskInfo.objects.all())

# 将自定义信号my_signals 和回调函数mySignal进行绑定
my_signals.connect(mySignal)
# 如果一个信号有多个回调函数,使用connect 和 disconnect进行切换
# my_signals.disconnect(mySignal)
views.py
# 同步视图调用同步任务 
from web1.signalsw import my_signals
def synView(request):
    my_signals.send(sender='MYSignal', name='cooper', age=18)   # 同步函数中使用
    syns()
    return HttpResponse('Hello this is syns')

举例 ——订单创建和取消

from django.db.models.signals import post_save
from app1.models import ProductInfo, OrderInfo

def signal_orders(sender, **kwargs):
    print('pre_save is coming.')
    print('kwargs', kwargs)
    # instance 代表当前修改或者新增的对象
    instance = kwargs.get('instance')
    # created 代表当前操作是否在模型中新增数据, 如新增则为True
    created = kwargs.get('created')
    # using 代表当前使用的数据库
    # 如果连接了多个数据库,则显示当前修改的数据表所在的数据库名称
    using = kwargs.get('using')
    # update_fields 控制需要更新的字段 默认是None
    update_fields = kwargs.get('update_fields')

    if instance.state == 0:  # 说明订单被取消,商品数量+1
        p = ProductInfo.objects.get(id=instance.product_id)
        p.number += 1
        p.save()
    elif instance.state == 1:
        p = ProductInfo.objects.get(id=instance.product_id)
        p.number -= 1
        p.save()
    print('all product', ProductInfo.objects.all())

post_save.connect(signal_orders, sender=OrderInfo)
def createView(request):
    product_id = request.GET.get('product_id', '')
    buyer = request.GET.get('buyer', '')
    if product_id and buyer:
        o = OrderInfo(product_id=product_id, buyer=buyer)
        o.save()
        return HttpResponse('订单被创建')
    return HttpResponse('参数无效')


def cancelView(request):
    id = request.GET.get('id', '')
    if id:
        o = OrderInfo.objects.get(id=int(id))
        if o.state == 1:
            o.state = 0
            o.save()
            return HttpResponse('订单被取消')
        return HttpResponse('订单未被取消')
    return HttpResponse('参数无效')
  • 15
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值