大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。
Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺
Django
一、介绍
1、简介
是用python语言写的开源web开发框架,并遵循MVC设计。
Django的主要目的是简便、快速的开发数据库驱动的网站。
2、特点
1) 重量级框架
2)MVT模式
- MVC
- 其核心思想是分工、解耦,让不同的代码块之间降低耦合,增强代码的可扩展性和可移植性,实现向后兼容。
- M全拼为Model,主要封装对数据库层的访问,对数据库中的数据进行增、删、改、查操作。
- V全拼为View,用于封装结果,生成页面展示的html内容。
- C全拼为Controller,用于接收请求,处理业务逻辑,与Model和View交互,返回结果。
- MVT
- M全拼为Model,与MVC中的M功能相同,负责和数据库交互,进行数据处理。
- V全拼为View,与MVC中的C功能相同,接收请求,进行业务处理,返回应答。
- T全拼为Template,与MVC中的V功能相同,负责封装构造要返回的html。
二、工程搭建
1、创建工程
创建工程命令
django-admin startproject 工程名称
工程目录说明
- settings.py 是项目的整体配置文件。
- urls.py 是项目的URL配置文件。
- wsgi.py 是项目与WSGI兼容的Web服务器入口。
- manage.py 是项目管理文件,通过它管理项目
运行开发服务器
python manage.py runserver ip:端口
或:python manage.py runserver
# 可以不写IP和端口,默认IP是127.0.0.1,默认端口为8000
2、创建子应用
创建
python manage.py startapp 子应用名称
# 例如创建一个user子应用模块
'''
cd ~/Desktop/code/demo
python manage.py startapp users
'''
子应用目录说明
- admin.py 文件跟网站的后台管理站点配置相关。
- apps.py 文件用于配置当前子应用的相关信息。
- migrations 目录用于存放数据库迁移历史文件。
- models.py 文件用户保存数据库模型类。
- tests.py 文件用于开发测试用例,编写单元测试。
- views.py 文件用于编写Web应用视图。
注册安装子应用
注册安装一个子应用的方法,即是将子应用的配置信息文件apps.py中的Config类添加到INSTALLED_APPS列表中。
# 例如
'''
INSTALLED_APPS = [
...
'users.apps.UsersConfig',
]
'''
3、创建视图
创建
# 在views.py中编写视图代码
from django.http import HttpResponse
def index(request):
"""
index视图
:param request: 包含了请求信息的请求对象
:return: 响应对象
"""
return HttpResponse("hello the world!")
'''
说明:
1、视图函数的第一个传入参数必须定义,用于接收Django构造的包含了请求数据的HttpReqeust对象,通常名为request。
2、视图函数的返回值必须为一个响应对象,不能像Flask一样直接返回一个字符串,可以将要返回的字符串数据放到一个HTTPResponse对象中。
'''
定义路由URL
-
- 在子应用中新建一个urls.py文件用于保存该应用的路由。
-
-
在users/urls.py文件中定义路由信息。
from django.conf.urls import urlfrom . import views
urlpatterns是被django自动识别的路由列表变量
urlpatterns = [
# 每个路由信息都需要使用url函数来构造
# url(路径, 视图)
url(r’^index/$’, views.index),
]
-
-
- 在工程总路由demo/urls.py中添加子应用的路由数据。
-
from django.conf.urls import url, include from django.contrib import admin urlpatterns = [ url(r'^admin/', admin.site.urls), # django默认包含的 # 添加 url(r'^users/', include('users.urls')), ] # 使用include来将子应用users里的全部路由包含进工程路由中 # r'^users/' 决定了users子应用的所有路由都已/users/开头,如我们刚定义的视图index,其最终的完整访问路径为/users/index/。 # include函数除了可以传递字符串之外,也可以直接传递应用的urls模块
-
- 启动运行
python manage.py runserver
- 启动运行
三、配置、静态文件与路由
1、 配置文件
BASE_DIR
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
- 当前工程的根目录,Django会依此来定位工程内的相关文件,我们也可以使用该参数来构造文件路径。
DEBUG
- 调试模式,创建工程后初始值为True,即默认工作在调试模式下。
- 作用:
- 修改代码文件,程序自动重启
- Django程序出现异常时,向前端显示详细的错误追踪信息.
- 注意:部署线上运行的Django不要运行在调式模式下,记得修改DEBUG=False .
本地语言与时区
-
中国大陆地区使用简体中文,时区使用亚洲/上海时区
LANGUAGE_CODE = ‘zh-hans’ # 语言
TIME_ZONE = ‘Asia/Shanghai’ # 时区
2、静态文件
- 推荐放在项目的根目录下,方便管理。
- 为了提供静态文件,需要配置两个参数:
- STATICFILES_DIRS 存放查找静态文件的目录
- STATIC_URL 访问静态文件的URL前缀
- 示例
1) 在项目根目录下创建static_files目录来保存静态文件。
2) 在demo/settings.py中修改静态文件的两个参数为
STATIC_URL = ‘/static/’
STATICFILES_DIRS = [
os.path.join(BASE_DIR, ‘static_files’),
]
3)此时在static_files添加的任何静态文件都可以使用网址 /static/文件在static_files中的路径 来访问了。 - 注意
Django 仅在调试模式下(DEBUG=True)能对外提供静态文件。
当DEBUG=False工作在生产模式时,Django不再对外提供静态文件,需要是用collectstatic命令来收集静态文件并交由其他静态文件服务器来提供。
3、路由说明
路由定义位置
- Django的主要路由信息定义在工程同名目录下的urls.py文件中,该文件是Django解析路由的入口。
- 每个子应用为了保持相对独立,可以在各个子应用中定义属于自己的urls.py来保存该应用的路由。然后用主路由文件包含各应用的子路由数据。
路由解析顺序
- Django在接收到一个请求时,从主路由文件中的urlpatterns列表中以由上至下的顺序查找对应路由规则,如果发现规则为include包含,则再进入被包含的urls中的urlpatterns列表由上至下进行查询。
- 值得关注的由上至下的顺序,有可能会使上面的路由屏蔽掉下面的路由,带来非预期结果。
需要注意定义路由的顺序,避免出现屏蔽效应。
路由命名与reverse反解析(逆向)
-
路由命名
- 在使用include函数定义路由时,可以使用namespace参数定义路由的命名空间,如
url(r’^users/’, include(‘users.urls’, namespace=‘users’)),
命名空间表示,凡是users.urls中定义的路由,均属于namespace指明的users名下。
命名空间的作用:避免不同应用中的路由使用了相同的名字发生冲突,使用命名空间区别开。 - 在定义普通路由时,可以使用name参数指明路由的名字,如
urlpatterns = [
url(r’^index/$’, views.index, name=‘index’),
url(r’^say’, views.say, name=‘say’),
]
- 在使用include函数定义路由时,可以使用namespace参数定义路由的命名空间,如
-
reverse反解析
使用reverse函数,可以根据路由名称,返回具体的路径,如:
from django.urls import reverse # 注意导包路径def index(request): return HttpResponse("hello the world!") def say(request): url = reverse('users:index') # 返回 /users/index/ print(url) return HttpResponse('say')
- 对于未指明namespace的,reverse(路由name)
- 对于指明namespace的,reverse(命名空间namespace:路由name)
路径结尾斜线/的说明
Django中定义路由时,通常以斜线/结尾,其好处是用户访问不以斜线/结尾的相同路径时,Django会把用户重定向到以斜线/结尾的路径上,而不会返回404不存在 。
urlpatterns = [
url(r'^index/$', views.index, name='index'),
]
四、请求与相应
1、请求
1)URL路径参数
可以使用正则表达式提取参数的方法从URL中获取请求参数,直接传递到视图的传入参数中。
-
未命名参数按定义顺序传递
url(r’^weather/([a-z]+)/(\d{4})/$’, views.weather),
def weather(request, city, year):
print(‘city=%s’ % city)
print(‘year=%s’ % year)
return HttpResponse(‘OK’) -
命名参数按名字传递
url(r’^weather/(?P[a-z]+)/(?P\d{4})/$’, views.weather),
def weather(request, year, city):
print(‘city=%s’ % city)
print(‘year=%s’ % year)
return HttpResponse(‘OK’)
2)QueryDict对象
定义在django.http.QueryDict
HttpRequest对象的属性GET、POST都是QueryDict类型的对象
QueryDict类型的对象用来处理同一个键带有多个值的情况
- 方法get():根据键获取值
dict.get(‘键’,默认值)
可简写为
dict[‘键’]
# 说明:
# 如果一个键同时拥有多个值将获取最后一个值
# 如果键不存在则返回None值,可以设置默认值进行后续处理 - 方法getlist():根据键获取值,值以列表返回,可以获取指定键的所有值
如果键不存在则返回空列表[],可以设置默认值进行后续处理
dict.getlist(‘键’,默认值)
3)查询字符串
通过request.GET属性获取,返回QueryDict对象。
# /qs/?a=1&b=2&a=3
def qs(request):
a = request.GET.get('a')
b = request.GET.get('b')
alist = request.GET.getlist('a')
print(a) # 3
print(b) # 2
print(alist) # ['1', '3']
return HttpResponse('OK')
# 重要:查询字符串不区分请求方式,即假使客户端进行POST方式的请求,依然可以通过request.GET获取请求中的查询字符串数据。
4)请求体
请求体数据格式:表单类型字符串、JSON字符串、XML字符串
请求方式:POST/PUT/PATCH/DELETE
-
表单类型
通过request.POST属性获取,返回QueryDict对象。
def get_body(request):
a = request.POST.get(‘a’)
b = request.POST.get(‘b’)
alist = request.POST.getlist(‘a’)
print(a)
print(b)
print(alist)
return HttpResponse(‘OK’)
# 重要:request.POST只能用来获取POST方式的请求体表单数据。 -
非表单类型
通过request.body属性获取最原始的请求体数据,自己按照请求体格式(JSON、XML等)进行解析。request.body返回bytes类型。
# 请求体中的数据
{“a”: 1, “b”: 2}import json def get_body_json(request): json_str = request.body json_str = json_str.decode() # python3.6 无需执行此步 req_data = json.loads(json_str) print(req_data['a']) print(req_data['b']) return HttpResponse('OK')
请求头
request.META为字典类型。
常见的请求头:
-
CONTENT_LENGTH – The length of the request body (as a string).
-
CONTENT_TYPE – The MIME type of the request body.
-
HTTP_ACCEPT – Acceptable content types for the response.
-
HTTP_ACCEPT_ENCODING – Acceptable encodings for the response.
-
HTTP_ACCEPT_LANGUAGE – Acceptable languages for the response.
-
HTTP_HOST – The HTTP Host header sent by the client.
-
HTTP_REFERER – The referring page, if any.
-
HTTP_USER_AGENT – The client’s user-agent string.
-
QUERY_STRING – The query string, as a single (unparsed) string.
-
REMOTE_ADDR – The IP address of the client.
-
REMOTE_HOST – The hostname of the client.
-
REMOTE_USER – The user authenticated by the Web server, if any.
-
REQUEST_METHOD – A string such as “GET” or “POST”.
-
SERVER_NAME – The hostname of the server.
-
SERVER_PORT – The port of the server (as a string).
具体使用:
def get_headers(request):
print(request.META[‘CONTENT_TYPE’])
return HttpResponse(‘OK’)
其他
- method:一个字符串,表示请求使用的HTTP方法,常用值包括:‘GET’、‘POST’。
- user:请求的用户对象。
- path:一个字符串,表示请求的页面的完整路径,不包含域名和参数部分。
- encoding:一个字符串,表示提交的数据的编码方式。
- 如果为None则表示使用浏览器的默认设置,一般为utf-8。
- 这个属性是可写的,可以通过修改它来修改访问表单数据使用的编码,接下来对属性的任何访问将使用新的encoding值。
- FILES:一个类似于字典的对象,包含所有的上传文件。
2、响应
HttpResponse
django.http.HttpResponse来构造响应对象
HttpResponse(content=响应体, content_type=响应体数据类型, status=状态码)
HttpResponse子类
Django提供了一系列HttpResponse的子类,可以快速设置状态码
- HttpResponseRedirect 301
- HttpResponsePermanentRedirect 302
- HttpResponseNotModified 304
- HttpResponseBadRequest 400
- HttpResponseNotFound 404
- HttpResponseForbidden 403
- HttpResponseNotAllowed 405
- HttpResponseGone 410
- HttpResponseServerError 500
JsonResponse
- 返回json字符串
- 设置响应头Content-Type为 application/json
redirect重定向
from django.shortcuts import redirect
def demo_view(request):
return redirect('/index.html')
3、Cookie
特点:
- 键值对格式的字符串,存储于浏览器中
- 基于域名安全,不同域名的Cookie是不能互相访问的,同源策略
- 浏览器会将cookie信息包含在请求报文中,发给服务器
设置Cookie
HttpResponse.set_cookie(cookie名, value=cookie值, max_age=cookie有效期)
# max_age 单位为秒,默认为None。如果是临时cookie,可将max_age设置为None。
读取Cookie
可以通过HttpRequest对象的COOKIES属性来读取本次请求携带的cookie值。request.COOKIES为字典类型。
def demo_view(request):
cookie1 = request.COOKIES.get('itcast1')
print(cookie1)
return HttpResponse('OK')
删除
request.delete_cokkie(键)
4、Session
- 写:request.session[‘键’]=值
- 读:request.session.get(‘键’,默认值)
- 删
- 删除值:request.session.clear()
- 删除整条数据:request.session.flush()
- 删除指定的键及值:del request.sessioon[‘键’]
- 设置有效期:request.session.set_expiry(value)
- 如果value是一个整数,session将在value秒没有活动后过期。
- 如果value为0,那么用户session的Cookie将在用户的浏览器关闭时过期。
- 如果value为None,那么session有效期将采用系统默认值,默认为两周,可以通过在settings.py中设置SESSION_COOKIE_AGE来设置全局默认值。
五、类视图与中间件
1、类视图
- 定义:使用类来定义的视图
- 好处:
- 可读性好
- 更高的复用性
- 使用
from django.view.generic import View
配置路由时,使用类视图的as_view()方法来添加 - 原理
在注册路由规则时类.as_view()返回方法名
在请求路径与正则匹配成功后,方法会被执行
调用dispatch()方法
根据请求方式名称获取方法
执行方法 - 使用装饰器
-
在URL配置中装饰
此种方法会为类视图中的所有请求方法都加上装饰器行为,不建议使用 -
类视图中装饰
method_decorator装饰器使用name参数指明被装饰的方法
# 为全部请求方法添加装饰器
@method_decorator(my_decorator, name=‘dispatch’)
class DemoView(View):
def get(self, request):
print(‘get方法’)
return HttpResponse(‘ok’)def post(self, request): print('post方法') return HttpResponse('ok') # 为特定请求方法添加装饰器 @method_decorator(my_decorator, name='get') class DemoView(View): def get(self, request): print('get方法') return HttpResponse('ok') def post(self, request): print('post方法') return HttpResponse('ok')
如果需要为类视图的多个方法添加装饰器,但又不是所有的方法(为所有方法添加装饰器参考上面例子),可以直接在需要添加装饰器的方法上使用method_decorator
from django.utils.decorators import method_decorator# 为特定请求方法添加装饰器 class DemoView(View): @method_decorator(my_decorator) # 为get方法添加了装饰器 def get(self, request): print('get方法') return HttpResponse('ok') @method_decorator(my_decorator) # 为post方法添加了装饰器 def post(self, request): print('post方法') return HttpResponse('ok') def put(self, request): # 没有为put方法添加装饰器 print('put方法') return HttpResponse('ok')
-
- 类视图Mixin扩展类
2、中间件
Django中的中间件是一个轻量级、底层的插件系统,可以介入Django的请求和响应处理过程,修改Django的输入或输出。
-
代码实现
定义一个中间件工厂函数,然后返回一个可以别调用的中间件。
中间件工厂函数需要接收一个可以调用的get_response对象。
返回的中间件也是一个可以被调用的对象,并且像视图一样需要接收一个request对象参数,返回一个response对象。
def simple_middleware(get_response):
# 此处编写的代码仅在Django第一次配置和初始化的时候执行一次。def middleware(request): # 此处编写的代码会在每个请求处理视图前被调用。 response = get_response(request) # 此处编写的代码会在每个请求处理视图之后被调用。 return response return middleware
定义好中间件后,需要在settings.py 文件中添加注册中间件
MIDDLEWARE = [
…
‘users.middleware.my_middleware’, # 添加中间件
]
注意:Django运行在调试模式下,中间件init部分有可能被调用两次。 -
执行流程
- 在请求视图被处理前,中间件由上至下依次执行
- 在请求视图被处理后,中间件由下至上依次执行
六、模板
1、配置
settings.py配置文件中修改TEMPLATES中的DIRS
2、定义
3、渲染
- 调用模板两步骤:
- loader.get_template(模板文件在模板目录中的相对路径)–> 返回模板对象
- 模板对象.render(contex=None,request=None)–>返回渲染后的html文本字符串
- Django提供了一个函数render可以简写上述代码。
-
render(request对象, 模板文件路径, 模板数据字典)
from django.shortcuts import renderdef index(request):
context={‘city’: ‘北京’}
return render(request,‘index.html’,context)
-
4、语法
{
{变量}}-变量
{%for%}…{%endfor%}
{%if%}…{%elif%}…{%else%}…{%endif%}
== != < > <= >=比较运算符
and or not布尔运算符
注意:运算符左右两侧不能紧挨变量或常量,必须有空格
变量|过滤器:参数过滤器
- safe,禁用转义,告诉模板这个变量是安全的,可以解释执行
- length,长度,返回字符串包含字符的个数,或列表、元组、字典的元素个数。
- default,默认值,如果变量不存在时则返回默认值。
- date,日期,用于对日期类型的值进行字符串格式化,常用的格式化字符如下:
- Y表示年,格式为4位,y表示两位的年。
- m表示月,格式为01,02,12等。
- d表示日, 格式为01,02等。
- j表示日,格式为1,2等。
- H表示时,24进制,h表示12进制的时。
- i表示分,为0-59。
- s表示秒,为0-59。
value|date:“Y年m月j日 H时i分s秒”
{#…#}单行注释
{%comment%}…{%endcomment%}多行注释
5、模板继承
父模板:{%block%}…{%endblock%}
子模板:{%extends “父模板路径”%}
写在子模板文件的第一行
七、数据库
ORM框架:
- 通过类和类对象就能操作它对所对应的表格中的数据
- 根据设计的类自动生成数据库中的表格
数据库迁移:
数据库的操作
1.增
- save
对象.save() - create
模型类.objects.create()
2.查
- 基本查询
- get
查询单一结果,不存在抛出 模型类.DoesNotExist 异常 - all
多个结果 - count
查询结果数量
- get
- 过滤查询
- filter 过滤多个结果
- exclude 排除
- get 过滤单一结果
- 过滤条件的表达语法
属性名称__比较运算符=值- exact
- contains
- startwith、endwith
- isnull
- in
- gt/gte/lt/lte
- year/month/day/week_day/hour/minute/second
- F对象
F(
属性名)
属性比较 - Q对象
Q(属性名__运算符=值)
实现逻辑或or的查询,需要使用Q()对象结合|运算符
Q对象前可以使用~操作符,表示非not
# 例:查询阅读量大于20,或编号小于3的图书,只能使用Q对象实现
from django.db.models import Q
BookInfo.objects.filter(Q(bread__gt=20) | Q(pk__lt=3)) - 聚合函数
使用aggregate()过滤器调用聚合函数,返回字典类型的数据- Avg
- Count
- Max
- Min
- Sum
- 排序
- order_by
- 默认升序,-降序
- 关联查询
-
一到多:一对应的模型类对象.多对应的模型类名小写_set
b = BookInfo.objects.get(id=1)
b.heroinfo_set.all() -
多到一:多对应的模型类对象.多对应的模型类中的关系属性名
h = HeroInfo.objects.get(id=1)
h.hbook -
访问一对应的模型类关联对象的id
多对应的模型类对象.关联类属性_id -
关联过滤查询
-
由多模型类条件查询一模型类数据:
关联模型类名小写__属性名__条件运算符=值# 例句:查询图书,要求图书中英雄的描述包含"八" BookInfo.objects.filter(heroinfo__hcomment__contains='八')
-
由一模型类条件查询多模型类数据:
一模型类关联属性名__一模型类属性名__条件运算符=值# 查询图书阅读量大于30的所有英雄 HeroInfo.objects.filter(hbook__bread__gt=30)
-
-
3.改
- save
- update
使用模型类.objects.filter().update(),会返回受影响的行数
4.删
- 模型类对象delete
- 模型类.objects.filter().delete()
查询集 QuerySet
1、概念:从数据库中获取的对象集合
过滤器方法:
- all()
- filter()
- exclude()
- order_by()
- exists():判断查询集中是否有数据,有返回Ture,无返回False
2、特性
- 惰性执行:创建查询集的时候不会调用数据库,调用数据的时候访问,迭代、序列化、if合用
- 缓存
3、限制查询集
对查询集进行下标或切片操作,切片后返回新的查询集,不会立即执行
管理器Manageer
1、自定义管理器
-
修改原始查询集,重写all()方法
-
booktest/models.py文件中
#图书管理器
class BookInfoManager(models.Manager):
def all(self):
#默认查询未删除的图书信息
#调用父类的成员语法为:super().方法名
return super().filter(is_delete=False) -
在模型类BookInfo中定义管理器
class BookInfo(models.Model):
…
books = BookInfoManager() -
使用方法
BookInfo.books.all()
-
-
在管理器类中补充定义新的方法
- 打开booktest/models.py文件,定义方法create
- 为模型类BookInfo定义管理器books语法如下
- 调用方法
八、Admin
使用
1、管理界面本地化
settings.py中设置语言和时间
2、创建超级管理员
python manage.py createsuperuser
> 修改密码:
>
> python manage.py changepassword 用户名
3、App应用配置
在settings.py中INSTALLED_APPS列表中添加此类
4、注册模型类
# 在booktest/admin.py 文件中编写代码:
from django.contrib import admin
from booktest.models import BookInfo,HeroInfo
admin.site.register(BookInfo)
admin.site.register(HeroInfo)
5、定义与使用Admin管理类
定义:继承自admin.ModelAdmin
使用:
-
注册参数
admin.site.register(BookInfo,BookInfoAdmin) -
装饰器
@admin.register(BookInfo)
class BookInfoAdmin(admin.ModelAdmin):
pass
编辑
1、调整列表页展示
-
页大小:list_per_page = 100
-
操作选项的位置:
- actions_on_top = True
- actions_on_bottom = True
-
列表中的列:list_display = [模型字段1,模型字段2]
-
将方法作为列:short_description属性
# models.py中:
class BookInfo(models.Model):
…
def pub_date(self):
return self.bpub_date.strftime(’%Y年%m月%d日’)pub_date.short_description = '发布日期' # 设置方法字段在admin中显示的标题 # admin.py中: class BookInfoAdmin(admin.ModelAdmin): ... list_display = ['id','btitle','pub_date'] # 说明: 方法列是不能排序的,需要排序: admin_order_field = 模型类字段
-
关联对象
在模型类中封装方法,访问关联对象的成员 -
右侧栏过滤器:list_filter = []
-
搜索框:search_fields = []
2、调整编辑页展示
- 显示字段:fields = []
- 分组显示:
fieldset=(
(‘组1标题’,{‘fields’:(‘字段1’,‘字段2’)}),
(‘组2标题’,{‘fields’:(‘字段3’,‘字段4’)}),
) - 关联对象
- 类型InlineModelAdmin:表示在模型的编辑页面嵌入关联模型的编辑
- 子类TabularInline:表格的形式嵌入
- 子类StackedInline:块的形式嵌入
3、调整站点信息
- admin.site.site_header 设置网站页头
- admin.site.site_title 设置页面标题
- admin.site.index_title 设置首页标语
4、上传图片
-
pip install Pillow==4.1.0
-
配置
MEDIA_ROOT=os.path.join(BASE_DIR,“static_files/media”) -
模型类添加字段
class BookInfo(models.Model):
…
image = models.ImageField(upload_to=‘booktest’, verbose_name=‘图片’, null=True)# upload_to 指明该字段的图片保存在MEDIA_ROOT目录中的哪个子目录
数据库迁移:
python manage.py makemigrations
python manage.py migrate
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/195900.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...