ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Django从零到项目跑通:虚拟环境、App构建与数据库迁移全攻略

Django从零到项目跑通:虚拟环境、App构建与数据库迁移全攻略 Django创建项目从命令到跑通一篇讲透创建阶段的每一步“Django创建项目”这个话题几乎是每个Python Web开发者入门的必经之路。但有意思的是越是基础的操作越容易在细节上翻车虚拟环境装好了没项目和应用到底什么关系为什么pycharm里点了几下创建出来的目录和别人教程里不一样static文件路径配了但图片就是显示不出来这些问题我这些年见过太多次了。这篇就把Django创建项目的完整流程拆开揉碎从环境准备、命令执行到目录结构、app创建、配置改动再到常见的报错排查一次性讲清楚。无论你是刚接触Python后端的新手还是已经被各种教程绕晕的初学者这篇都适合你照着一步步操作。先交代一下我的使用习惯本篇文章以Django 4.x版本为主Python环境推荐3.10或更高版本。为什么用4.x因为它是目前最主流的生产级版本既有长期维护保障又覆盖了新特性。如果你还在用Django 2.x建议尽快升级后面我讲的一些配置写法在老版本里是行不通的。1. 动手前的准备环境、工具链与项目规划1.1 虚拟环境项目隔离是第一原则我见过太多“跑不起来”的案例最后排查下来十有八九是环境问题。Django项目必须跑在虚拟环境里这不是可选项而是必选项。原因很简单不同项目依赖的Django版本可能不同比如老项目用Django 3.2新项目用Django 5.0如果不做隔离两个项目的依赖互相污染轻则版本冲突重则直接启动报错。创建虚拟环境有两种主流方式# 方式一Python自带的venv python3 -m venv venv # 方式二Anaconda环境如果你用conda管理Python conda create -n mydjango python3.10选择哪种取决于你平时怎么管理Python。用Anaconda的直接用conda创建环境即可注意别让conda基环境和项目环境混在一起用我之前见过有人在base环境里直接pip install django装了一堆乱七八糟的包最后项目依赖全乱了。用原生Python的venv就够了轻量干净。激活环境后再安装Django# Windows venv\Scripts\activate pip install django # macOS / Linux source venv/bin/activate pip install django装完验证一下python -m django --version如果能正常输出版本号说明环境OK。这一步看似简单但它决定了后面所有操作能不能顺利进行。我建议每次新建项目前都先确认当前在哪个环境里避免“明明装了Django但import不了”的尴尬。1.2 项目与应用两个必须分清的“容器”刚接触Django的人最容易混淆的概念就是“项目project”和“应用app”。这里用一个生活化类比来讲项目是一个“公司”应用是公司里的“部门”。公司负责整体的资源配置、规章制度、对外接口部门各自负责具体业务模块比如人事部管员工、财务部管账目。项目是整个网站的载体应用是网站里一个个功能模块。所以创建项目的顺序一定是先建“公司”再根据需要建“部门”。Django官方建议一个项目里可以有多个应用每个应用职责单一。比如一个电商网站项目可以有商品应用、订单应用、用户应用各管各的互不干扰。养成这种模块化思维后面项目变大时才知道怎么组织代码。以我个人的习惯创建项目前会先画一个简单的功能清单想想这个网站需要哪些模块对应建哪些app。这一步不花太多时间但能避免后面代码越写越乱。比如这次要做的项目一开始就规划好需要哪些应用再动手创建后面改起来省心得多。2. 项目创建全解析命令行是基本功2.1 用django-admin startproject创建项目在虚拟环境激活的状态下找一个合适的目录我一般在~/Projects或Windows下的某个工作目录里执行django-admin startproject mysite执行完以后你会看到生成了一个mysite文件夹里面是这样mysite/ manage.py mysite/ __init__.py settings.py urls.py asgi.py wsgi.py很多人第一次看到这个结构会有点懵怎么里面还有一个同名文件夹这里拆解一下。外层的mysite/是项目的容器文件夹名字其实可以随意改它只是承载项目文件的目录。内层的mysite/是Python包是真正的项目核心代码这个名字不建议改因为所有配置导入都依赖它。逐个说一下这些文件的作用文件作用manage.pyDjango项目管理入口所有命令都是通过它执行的比如启动服务器、迁移数据库、创建appsettings.py全局配置中心数据库、应用注册、模板路径、静态文件、时区、语言等都在这里配置urls.pyURL路由入口负责把用户访问的网址映射到对应的视图函数asgi.py异步网关接口部署时配合ASGI服务器使用在Django 3.0以后加入wsgi.py同步网关接口传统部署Gunicorn、uWSGI用这个__init__.pyPython包的初始化文件标识这个目录是一个Python包这里有个很容易被忽视的细节django-admin和manage.py都能执行很多管理命令但两者有区别。django-admin是全局命令只要Django装了就能用manage.py是项目内的命令它会在执行前自动加载当前项目的设置。所以凡是在项目目录内一律习惯用python manage.py而不是django-admin否则可能出现命令跑在了错误环境或者找不到项目配置的问题。2.2 IDE方式创建PyCharm与VSCode的差异命令行创建是基本功但实际开发里很多人用IDE创建项目。这里讲一下两种常用IDE的区别帮你避坑。PyCharm专业版自带Django支持新建项目时直接选择Django模板它会帮你把虚拟环境、Django安装、初始项目结构一次性弄好。这个方式适合完全从零起步、用鼠标操作的新手。但要注意两点第一PyCharm创建项目时会默认带上一个templates目录和static目录这是它帮你预先规划好的不是Django的标准目录需要自己在settings.py里配置才生效第二PyCharm的虚拟环境路径有时候会变成硬编码如果你把项目文件夹移动了位置解释器可能失效需要重新配置。VSCode则更轻量它本身不自带Django模板需要你先在命令行创建好项目再用VSCode打开文件夹。VSCode的好处是插件生态丰富配合Python插件和Django插件写代码时能获得很好的智能提示。很多教程里推荐VSCode本质是因为它把“创建”这件事交给了命令行让开发者理解真正发生了什么而不是被IDE的向导遮住。我个人的实战建议是用命令行创建项目用VSCode或PyCharm写代码。这样既保证了你对项目结构的掌控感又能享受IDE的便利。创建这个动作本身很简单不需要让IDE替你做决定。2.3 目录结构为什么长这样核心配置的底层逻辑Django项目创建出来以后不管什么版本核心结构基本不变因为这是Django框架设计者定下的规则。理解了这些文件之间的协作关系你就理解Django的运行机制了。settings.py是全局配置中心它由大量配置项组成最重要的几个# 已注册的应用 INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, ] # 数据库配置 DATABASES { default: { ENGINE: django.db.backends.sqlite3, NAME: BASE_DIR / db.sqlite3, } } # 语言与时区 LANGUAGE_CODE en-us TIME_ZONE UTC初始状态下Django预置了7个应用包括admin后台、认证系统、会话管理、消息框架、静态文件管理等。这些是Django开箱即用的组件。但并不说你必须一直用它比如有些项目用自定义的用户模型会把django.contrib.auth替换掉。BASE_DIR是另一个关键变量它在settings.py开头定义BASE_DIR Path(__file__).resolve().parent.parent这个写法是获取当前文件settings.py的上级目录的上级目录也就是项目根目录。后面配置模板、静态文件、媒体文件路径时几乎都要基于BASE_DIR来拼接保证项目在不同电脑上移动时路径都不会出错。urls.py是URL路由入口初始状态下长这样from django.contrib import admin from django.urls import path urlpatterns [ path(admin/, admin.site.urls), ]这个文件定义了访问地址和视图函数的映射关系。path(admin/, admin.site.urls)的意思就是当用户访问网站域名/admin/时交给Django内置的admin模块处理。后面你自己写的app也是通过这个文件来挂载路由的。3. 创建应用App让项目真正拥有业务功能3.1 startapp命令与App目录结构项目创建好了现在要往里添加业务功能模块。以创建一个博客应用为例python manage.py startapp blog执行之后项目目录里会多出一个blog文件夹blog/ __init__.py admin.py apps.py models.py tests.py views.py migrations/ __init__.py逐个说下这些文件的作用文件作用models.py定义数据库模型一张表对应一个类views.py定义视图函数或视图类处理业务逻辑admin.py把模型注册到Django后台让管理员可以增删改查数据apps.py应用的配置类Django用它识别这个应用的信息tests.py写测试代码的地方migrations/数据库迁移记录目录每次修改模型后生成迁移文件创建完app后第一件事是把应用注册到项目里。在settings.py的INSTALLED_APPS列表中加入应用的配置类INSTALLED_APPS [ # 项目自带的app django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, # 你自己创建的第三方app blog.apps.BlogConfig, ]blog.apps.BlogConfig这种写法是在告诉Django这个应用从blog包下的apps.py文件里的BlogConfig类中读取配置。如果你觉得这一行太长也可以直接写blogDjango会自动去找默认的配置类。两种写法都可以但建议用第一种因为显式指定配置类更规范后续如果自定义应用配置也会更方便。3.2 注册app时最容易被忽略的细节注册app这一步看着简单但有一个坑我要专门提醒如果你在写模型之前就运行了makemigrationsDjango会发现你还没有注册应用直接给你一个No installed app with label blog的报错。这个报错信息很容易让人误以为是模型写错了其实是应用注册漏了。另一个细节是app在INSTALLED_APPS里的顺序问题。Django的app加载是有一定的依赖顺序的通常情况下你把自己的app放在列表靠后的位置不会出错但如果你用了某些依赖前置顺序的第三方包可能就需要调整位置。比如使用django-cors-headers这类中间件时官方文档会明确要求它在某个位置否则请求就会过不了。所以注册app时最好先看一眼这个app的文档有没有特殊要求。还有一个和app名称相关的问题startapp创建的app名字必须是合法的Python标识符不能用横杠、空格、中文。我试过有人用my-blog这种名字创建app直接报错。如果确实需要这样的组件名可以做一个别名包来包装但这属于更高级的玩法新手阶段不建议碰。3.3 创建app后快速验证创建完app并注册之后建议立刻验证一下能不能正常访问。先用一个最简单的视图测试在blog/views.py里写from django.http import HttpResponse def index(request): return HttpResponse(Hello Django!)然后在blog目录下新建一个urls.pyfrom django.urls import path from . import views urlpatterns [ path(, views.index, nameindex), ]最后在项目的urls.py里把blog的路由挂载进来from django.urls import include, path urlpatterns [ path(admin/, admin.site.urls), path(blog/, include(blog.urls)), ]这样做的意义是让你在动手写完整的业务逻辑之前先把“请求到视图”的链路跑通。很多初学者喜欢一下子把模型、视图、模板全部写完结果出错时不知道问题出在哪个环节。而分步验证的习惯能帮你快速定位问题。启动服务器python manage.py runserver访问http://127.0.0.1:8000/blog/看到Hello Django!说明项目到app的整体链路已经打通了。接下来就可以放心地写业务了。4. 数据库、模型与后台配置项目能存数据的基石4.1 默认SQLite与生产数据库的选择Django创建项目时默认配置的是SQLite数据库DATABASES { default: { ENGINE: django.db.backends.sqlite3, NAME: BASE_DIR / db.sqlite3, } }SQLite是一个轻量级的嵌入式数据库整个数据库就是一个文件不需要单独的数据库服务。开发阶段用它非常方便不需要安装任何东西也不需要配置用户名密码。所以新手阶段我强烈建议就用默认的SQLite把精力放在学习Django本身而不是被数据库运维分散注意力。但也要明白SQLite的局限并发写入能力弱不适合高并发场景也不太适合多服务器部署。如果你开发的是一个有可能上线的项目建议从一开始就选好生产数据库。主流选择是PostgreSQL或MySQL。以MySQL为例配置改成DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: mydb, USER: root, PASSWORD: password, HOST: 127.0.0.1, PORT: 3306, } }换成MySQL后需要安装驱动pip install mysqlclient换数据库的最佳时机是在写模型之前。因为迁移文件一旦生成是和数据库绑定的中途换数据库可能会出现迁移记录对不上的问题。我自己的习惯是如果项目预期只是一个Demo或学习项目就用SQLite如果预期会部署上线、有多个用户访问就一开始配好PostgreSQL或MySQL免得后面返工。4.2 定义模型与数据库迁移流程数据模型是Django应用的核心。还是以博客为例在blog/models.py里写一个简单的Article模型from django.db import models class Article(models.Model): title models.CharField(max_length200) content models.TextField() pub_date models.DateTimeField(auto_now_addTrue) class Meta: ordering [-pub_date] def __str__(self): return self.title这段代码定义了一张blog_article表表里有三个字段标题、内容、发布时间。auto_now_addTrue表示发布日期自动设置为创建时间。写完后执行两条命令python manage.py makemigrations python manage.py migratemakemigrations会根据模型变化生成迁移文件migrate则把迁移文件真正应用到数据库。执行完以后Django会在blog/migrations/目录下生成一个类似0001_initial.py的文件里面的内容记录了这张表的字段定义。这个过程必须理解透彻因为它是Django对数据库管理的核心机制。迁移文件相当于一个“变更日志”每次修改模型都生成一个新的迁移文件记录这次的改动。migrate命令负责把这些改动同步到数据库。这种机制保证了团队协作时每个人都能用一致的数据库结构而不是靠手工改表。需要提醒的是不要手动去数据库里改表结构。如果改了Django的迁移记录会和你实际的数据库结构不一致后面执行migrate时可能会报错或者跳过迁移。一切数据库结构的变更都从模型代码出发让Django工具链去处理。4.3 后台管理Django自带的后台有多好用Django自带一个功能强大的后台管理系统只要注册一下模型就能在后台直接增删改查数据。在blog/admin.py里写from django.contrib import admin from .models import Article admin.register(Article) class ArticleAdmin(admin.ModelAdmin): list_display (title, pub_date) search_fields (title,)然后创建一个管理员账号python manage.py createsuperuser按提示输入用户名、邮箱可留空、密码。启动服务器访问http://127.0.0.1:8000/admin/登录以后就能看到Article管理界面可以直接添加文章、编辑内容、删除记录。后台管理这个功能在项目初期特别有用。不需要写任何前端页面就能完成数据的录入和操作。很多初学者会忽略这个能力觉得后台管理是“附加功能”其实它是快速原型验证利器。你要做的业务数据模型定好以后先用后台把数据录进去再写前端页面整个开发效率会高很多。4.4 数据的增删改查ORM操作与常用写法掌握了后台之后你还应该知道如何用代码操作数据。Django的ORM提供了非常简洁的API结合热搜词里提到的“执行查询-删除对象”我列几个最常用的操作# 创建数据两种方式 article Article(title标题, content内容) article.save() # 或者 article Article.objects.create(title标题, content内容) # 查询所有数据 articles Article.objects.all() # 条件查询 articles Article.objects.filter(title__containsDjango) # 查询单条数据查不到会报 DoesNotExist 异常 article Article.objects.get(id1) # 更新数据 article.title 新的标题 article.save() # 或者批量更新 Article.objects.filter(title旧标题).update(title新标题) # 删除对象 article Article.objects.get(id1) article.delete() # 批量删除 Article.objects.filter(title旧标题).delete()关于get方法有个重要的点objects.get()如果查询不到数据会抛出异常如果查到多条也会抛出异常。所以当你不能确定数据唯一性时优先用filter().first()它不会因为数据不存在而报错而是返回None更安全。再讲一下objects的来源。每个继承自models.Model的类都会自动获得一个objects属性这就是Manager对象它是Django ORM的入口。你可以通过自定义Manager来扩展查询方法比如加一个published_articles Article.published_manager.all()之类的但新手阶段用好默认的objects就足够了。5. 路由、视图与模板把数据展示到浏览器5.1 URL路由从路径到视图的映射规则前面已经提到过Django的URL路由由urls.py管理。当用户访问一个URL时Django会从根路由开始逐层匹配找到对应的视图函数。一个完整的URL配置由四部分组成path(articles/int:pk/, views.article_detail, namearticle_detail)路由规则articles/int:pk/尖括号里的内容是路径参数int:pk表示匹配一个整数并把它命名为pk传给视图视图函数views.article_detail这是blog/views.py里对应的函数路由名namearticle_detail这个名字可以在模板里的{% url %}标签中使用也可以用于视图中的反向解析Django的路由还有一个很重要的特性include函数支持把子路由分发到不同app的urls.py里。前面已经用过这个方式再强调一下它的价值——各app之间的路由互相隔离项目大了以后代码不会全部堆在根urls.py里维护变得简单。写路由的时候容易犯的错是路径重复。比如你在根路由挂载了path(blog/, include(blog.urls))那么blog应用里的路由就不需要再写blog前缀了直接在应用内写path(, views.index)即可。如果两个地方都写了blog最终的访问路径就会变成/blog/blog/很难看也容易搞混。5.2 视图与模板render函数的作用视图函数负责业务逻辑最终要把处理结果渲染成HTML页面返回给用户。最常用的写法是from django.shortcuts import render, get_object_or_404 from .models import Article def article_detail(request, pk): article get_object_or_404(Article, pkpk) return render(request, blog/article_detail.html, {article: article})render函数做了三件事加载模板、填充上下文数据、返回一个HttpResponse对象。这是Django最常用的响应方式我在几乎所有项目里都会用到它。这里顺带讲一下get_object_or_404它是get的升级版如果查不到数据会直接返回404错误页面而不是抛出异常。这样用户体验更好用户访问一个不存在的文章时看到的是友好的404而不是服务器错误。在Django中视图函数还可以用类视图来写。类视图是更高级的封装比如ListView、DetailView这些通用视图能帮我们省掉大量重复代码。但新手阶段我建议先用函数视图把流程走通理解了HTTP请求-响应的本质再进阶学习类视图不迟。5.3 模板系统基础基础语法与模板继承Django的模板系统简单说就是在HTML里嵌入模板语法让页面能动态展示数据。上面那个例子中的blog/article_detail.html模板可以这样写!DOCTYPE html html head title{{ article.title }}/title /head body h1{{ article.title }}/h1 p{{ article.pub_date }}/p div{{ article.content }}/div /body /html{{ article.title }}是变量输出{% for %}是循环标签{% if %}是条件标签这是模板系统的三大基础语法。模板系统还提供了一种组织页面的能力——模板继承。你可以写一个base.html作为整个网站的骨架里面用{% block content %}{% endblock %}挖一个坑其他页面通过{% extends base.html %}继承这个骨架然后填充自己的内容块。这样每个页面的公共部分比如导航栏、页脚就不用重复写了。这是我从第一天用Django就养成的习惯项目越大模板继承带来的维护便利就越明显。5.4 static文件与模板加载图片显示不了的排查思路很多人会遇到这类问题在VSCode里写了img src/static/img/logo.png但页面打开就是显示不了图片。这里把Django静态文件的机制讲透。首先在settings.py里确认静态文件的配置。Django默认有STATIC_URL /static/这告诉Django所有以/static/开头的URL都映射到静态文件。但STATIC_URL只是URL前缀真正决定从磁盘哪里找文件的是STATICFILES_DIRS。默认情况下每个app的static/目录会被自动识别为静态文件目录。也就是说如果你的app是blog你在blog/static/blog/下放一个style.css然后用/static/blog/style.css访问就能访问到。这里的关键点是Django自动扫描的是每个app下的static目录不是项目根目录下的static目录。如果你在项目根目录下建了一个static文件夹希望它作为全局静态文件目录那么必须手动配置import os STATIC_URL /static/ STATICFILES_DIRS [ BASE_DIR / static, ]配置好以后项目根目录下的static文件夹里的文件就能被正常访问了。但这里有一个隐藏的坑如果这个static目录在开发服务器下能访问但部署到服务器上放到了nginx之类的地方可能还需要配置STATIC_ROOT。STATIC_ROOT是执行python manage.py collectstatic时把所有静态文件收集到同一个目录用的。平时开发时不需要它部署时必须配置。所以如果你在VSCode里写了img标签但在Django的static文件中显示不了最可能的排查路径就是检查文件是否在正确的位置是app/static/app/下还是项目根目录的static/下检查STATICFILES_DIRS是否配置如果用的是项目根目录static这个必须配检查URL是否正确/static/是前缀后面接文件的相对路径检查页面和服务器是否报错打开浏览器F12的Network面板看是否有404这个排查思路是我自己在项目的实际使用中反复验证过的基本能覆盖90%以上的静态文件显示问题。6. 常见问题与排查技巧实录6.1 创建项目阶段的高频报错速查表下面整理一份我在这几年里最常遇到的、和Django创建项目相关的报错清单报错信息原因解决方法django-admin: command not foundDjango未安装或环境未激活pip install django确认虚拟环境已激活ModuleNotFoundError: No module named djangoPython环境不对Django装到了别的环境which python检查当前环境重新安装DjangoCommandError: mysite conflicts with the name of an existing Python module项目名和已有Python包冲突换一个项目名TypeError: Settings object is not subscriptablesettings.py写了错误的配置类型检查配置项是否是字典类型No installed app with label blogapp没有在INSTALLED_APPS中注册在settings.py中注册appdjango.db.utils.OperationalError: no such table: blog_article模型创建时未执行migrate执行python manage.py makemigrations和migrateImproperlyConfigured: The STATICFILES_DIRS setting should not contain the STATIC_ROOT settingSTATICFILES_DIRS和STATIC_ROOT配置了同一目录确保两个配置指向不同目录Error: That port is already in use8000端口被占用使用python manage.py runserver 8001换个端口每次排查问题我第一件事就是看报错信息的前几行因为Python的报错往往会把最核心的原因放在最前面。如果看不懂报错英文就逐词翻译慢慢就会熟悉这些报错格式了。6.2 环境差异带来的一类典型问题用Anaconda创建虚拟环境、然后通过PyCharm创建Django项目时很可能遇到环境识别的问题。PyCharm默认会使用它识别的Python解释器如果你之前在conda里装了Django但PyCharm选的是系统默认的Python那项目创建出来后没有Django环境运行不了。解决办法是在PyCharm的Settings Project Python Interpreter里选择conda环境的Python解释器路径在anaconda3/envs/你的环境名/bin/python或anaconda3/envs/你的环境名/python.exe然后确认Django包已经安装。这个问题的本质是环境路径不一致。我把这个经验写在这里是因为它和我上面提到的“创建前先确认环境”是同一个原则只是IDE会把环境选择的过程藏在向导后面一不小心就会选错。6.3 开发效率小技巧runserver的自动重载与调试利器最后分享一个实用技巧。runserver默认开启了自动重载auto-reload也就是你修改Python代码后保存服务器会自动重启不用手动CtrlC再启动。但要注意这个自动重载不是万能的比如你修改了模板文件或者static文件它不会自动生效刷新页面即可。还有就是你在调试循环里改了代码有时候重载会卡住此时就需要手动重启。另一个提高效率的方法是在代码里加print或使用logging模块。很多人遇到数据没显示的问题时第一反应是检查模板但往往问题出在后端没有把数据传过来。在这种情况下在视图函数里加print(article)能看到控制台输出就说明后端没问题然后去查模板变量对不对。调试模式下还可以用Django的django-debug-toolbar这个第三方包它能直接在页面上显示SQL查询、请求头、模板渲染时间等调试信息非常强大。安装方式pip install django-debug-toolbar然后在settings.py里配置在INSTALLED_APPS中加入debug_toolbar在urls.py中加入路由。具体配置官方文档写得很清楚我用下来最大的感受是查数据库问题时它让我一眼看到底执行了哪些SQL、有没有N1查询省去了大量的排查时间。写在最后的实操体会这篇文章从环境准备、项目创建、app创建、数据库配置、视图模板路由一直讲到了常见报错排查基本覆盖了Django创建项目的完整链路。回头看这个过程中最核心的其实是理解“项目是容器、app是模块、配置驱动一切”这三个概念。只要把settings.py当成项目的总指挥把urls.py当成路由器把models.py当成数据库结构定义后面所有功能都是在这个框架里添砖加瓦。如果让我给刚起步的开发者一个建议那就是不要只是跟着教程敲一遍命令而是要亲手创建三到五个不同规模的项目。每个项目你都会因为一遍遍重复这些基础操作慢慢形成肌肉记忆。等哪一天你不看文档也能流利地创建项目、创建app、配好数据库和静态文件那你就不再是一个看教程起步的新手了。我现在开发新项目时仍然习惯先从命令行敲一遍django-admin startproject开始。不是因为我不知道IDE可以一键创建而是这个动作让我时刻提醒自己框架只是一个工具真正重要的是理解它每一层的工作原理。等你上手之后你也可能会发现这一点。
返回列表