Flask項目搭建及部署(完整版!全網最全)


轉發:https://blog.csdn.net/asd529735325/article/details/103011940

 

flask搭建及部署
pip 19.2.3

python 3.7.5

Flask 1.1.1

Flask-SQLAlchemy 2.4.1

Pika 1.1.0

Redis 3.3.11

flask-wtf 0.14.2

 

1、創建flask項目:

 

創建完成后整個項目結構樹:

 

 

app.py: 項⽬管理⽂件,通過它管理項⽬。

static: 存放靜態文件

templates文件夾:用於放置html模板文件

 

由於flask屬於輕量級web框架, 更加自由、靈活,可擴展性強,第三方庫的選擇面廣,開發時可以結合自己最喜歡用的輪子,也能結合最流行最強大的Python庫 。所以這個框架的代碼架構需要自己設計。

 

2、創建項目主要邏輯代碼保存目錄

 

 

手動創建application目錄、filter目錄及其子目錄

application : 項目主要邏輯代碼保存目錄

_init_.py : 創建flask應用並加載配置,如mysql,redis,rabbitmq,

apps : 專門用於保存每一個項目的藍圖

app1 : app1藍圖目錄,在app1下的init_.py中文件中創建藍圖對象,view.py中新增對應的視圖文件,在 model.py中寫模型代碼

settings : 項目配置存儲目錄

dev.py : 項目開發階段配置文件

prop.py : 項目生成階段配置文件

static : 項目靜態文件夾(用於存放css一類的文件)

templates : 用於放置html模板文件

filter : 整個項目攔截器目錄

requestFilter.py: 針對整個app項目全局路由攔截規則定義

app.py : 項⽬管理⽂件,通過它啟動整個項目

 

2.1 配置mysql數據庫,加載配置文件並針對整個app項目定義全局db
2.1.1 settings.py
#全局通用配置類
class Config(object):
"""項目配置核心類"""
#調試模式
DEBUG=False

# 配置日志
# LOG_LEVEL = "DEBUG"
LOG_LEVEL = "INFO"


# 配置redis
# 項目上線以后,這個地址就會被替換成真實IP地址,mysql也是
REDIS_HOST = 'your host'
REDIS_PORT = your port
REDIS_PASSWORD = 'your password'
REDIS_POLL = 10

#數據庫連接格式
SQLALCHEMY_DATABASE_URI = "mysql+pymysql://user:password@localhost:3306/test?charset=utf8"
# 動態追蹤修改設置,如未設置只會提示警告
SQLALCHEMY_TRACK_MODIFICATIONS = False
# 查詢時會顯示原始SQL語句
SQLALCHEMY_ECHO = False
# 數據庫連接池的大小
SQLALCHEMY_POOL_SIZE=10
#指定數據庫連接池的超時時間
SQLALCHEMY_POOL_TIMEOUT=10
# 控制在連接池達到最大值后可以創建的連接數。當這些額外的 連接回收到連接池后將會被斷開和拋棄。
SQLALCHEMY_MAX_OVERFLOW=2


#rabbitmq參數配置
RABBITUSER="user"
RABBITPASSWORD="password"
RABBITHOST="your ip"
RABBITPORT=your port

2.1.2 dev.py
from . import Config

class DevelopmentConfig(Config):
'開發模式下的配置'
# 查詢時會顯示原始SQL語句
SQLALCHEMY_ECHO = True
2.1.3 prop.py
from . import Config

class ProductionConfig(Config):
"""生產模式下的配置"""
DEBUG = False
2.1.4 加載配置文件,定義全局的db( SQLALchemy類的實例 )供項目使用
# 主應用的根目錄
app = Flask(__name__)

config = {
'dev': DevelopmentConfig,
'prop': ProductionConfig,
}

# 設置配置類
Config = config['dev']

# 加載配置
app.config.from_object(Config)

# 創建數據庫連接對象
db = SQLAlchemy(app)

dev : 測試環境配置

prop: 生產環境配置

 

Flask應用app配置加載

通常三種方式

從配置對象中加載:app.config.from_object()

從配置文件中加載:app.config.from_pyfile()-ini文件

從環境變量中加載:app.config.from_envvar()

 

配置對象

從配置對象中加載,創建配置的類:

# 配置對象,里面定義需要給 APP 添加的一系列配置
class Config(object):
DEBUG = True


app = Flask(__name__)

# 從配置對象中加載配置
app.config.from_object(Config)
app.run()

配置文件

從配置文件中加載,在目錄中定義一個配置文件config.ini

app = Flask(__name__)

# 從配置對象中加載配置
app.config.from_pyfile("config.ini")
app.run()

環境變量

app = Flask(__name__)
# 從環境變量中加載
app.config.from_envvar("FLASKCONFIG")
app.run()

 

2.2 定義model模型,負責和數據庫交互
app1.model

from application import db

class Wdtest(db.Model):
__tablename__ = "wdtest" #設置表名
id = db.Column(db.String(100), primary_key=True, comment="主鍵ID")
name = db.Column(db.String(20), index=True, comment="姓名" )
age = db.Column(db.Integer, default=True, comment="年齡")
模型 表示程序使用的持久化實體. 在Flask-SQLALchemy 中, 模型一般是一個 Python 類, 類中的屬性對應數據庫中的表.

db.Model :創建模型,

db.Column : 創建模型屬性.

tablename :指定表名

 

模型屬性類型 :

類型名 Python類型 說明
Integer int 普通整數,一般是 32 位
SmallInteger int 取值范圍小的整數,一般是 16 位
Big Integer int 或 long 不限制精度的整數
Float float 浮點數
Numeric decimal.Decimal 定點數
String str 變長字符串
Text str 變長字符串,對較長或不限長度的字符串做了優化
Unicode unicode 變長 Unicode 字符串
Unicode Text unicode 變長 Unicode 字符串,對較長或不限長度的字符串做了優化
Boolean bool 布爾值
Date datetime.date 日期
Time datetime.time 時間
DateTime datetime.datetime 日期和時間
Interval datetime.timedelta 時間間隔
Enum str 一組字符串
PickleType 任何 Python 對象 自動使用 Pickle 序列化
LargeBinary str 二進制文件
常用 SQLAlchemy 列選項

選項名 說明
primary_key 如果設為 True,這列就是表的主鍵
unique 如果設為 True,這列不允許出現重復的值
index 如果設為 True,為這列創建索引,提升查詢效率
nullable 如果設為 True,這列允許使用空值;如果設為 False,這列不允許使用空值
default 為這列定義默認值

2.3 聲明藍圖
app1._init.py

#給app取別名為 'index'
index_blu=Blueprint('index',__name__,template_folder='templates',static_folder='static')

from .views import *
template_folder:指定模板文件路徑,查找順序,先全局templates里面找,沒找到,再往子藍圖里面找.

這里是把view中所有的視圖都聲明在index這個藍圖里面,接下來我們需要做的是將這個聲明好的藍圖,注冊進我們的項目中。

2.4 將聲明好的藍圖注冊進app中
application.init_:

from application.settings.dev import DevelopmentConfig
from application.settings.prop import ProductionConfig

# 主應用的根目錄
app = Flask(__name__)

config = {
'dev': DevelopmentConfig,
'prop': ProductionConfig,
}

# 設置配置類
Config = config['dev']

# 加載配置
app.config.from_object(Config)

# 創建數據庫連接對象
db = SQLAlchemy(app)

# todo 注冊藍圖
from .apps.app1 import index_blu
app.register_blueprint(index_blu, url_prefix='/index')

針對:app = Flask(name)解釋

Flask類初始化參數

Flask類init方法部分代碼

def __init__(
self,
import_name,
static_url_path=None,
static_folder="static",
static_host=None,
host_matching=False,
subdomain_matching=False,
template_folder="templates",
instance_path=None,
instance_relative_config=False,
root_path=None,
):pass
import_name:Flask程序所在的包(模塊),傳 __name__

static_url_path:靜態文件訪問路徑,可以不傳,默認為:/ + static_folder

static_folder:靜態文件存儲的文件夾,可以不傳,默認為 static

template_folder:模板文件存儲的文件夾,可以不傳,默認為 templates

 

 

3 通過以上的步驟后,我們可以基本操作數據庫了:
以下所有示例代碼,皆在view.py中去實現

3.1 增:
先寫怎么增,然后增加,最后提交

student = Wdtest(id=ids , name=name, age=age)
try:
application.db.session.add(student)
application.db.session.commit()
except:
# 事務回滾
application.db.session.rollback()
3.2 刪:
先獲取數據庫中的這個數據,再刪除它

user = Wdtest.query.first()
application.db.session.delete(user)
application.db.session.commit()
3.3 改:
user = Wdtest.query.first()
user.name = name
try:
application.db.session.commit()
except:
# 事務回滾
application.db.session.rollback()
3.4 查:
# 查詢所有⽤戶數據
user_list=Wdtest.query.all()

# 查詢有多少個⽤戶
user_list_num=Wdtest.query.count()
# 查詢第1個⽤戶
user=Wdtest.query.first()
# 查詢id為3的⽤戶[3種⽅式]
user=Wdtest.query.get(3) # 根據主鍵查詢
user_list=Wdtest.query.filter_by(id=3).all() # 以關鍵字實參形式進行匹配字段
user_list=Wdtest.query.filter(Wdtest.id == 3).all() # 以恆等式形式匹配字段

# 查詢名字結尾字符為g的所有⽤戶
Wdtest.query.filter(Wdtest.name.endswith('g')).all()

# 查詢名字包含‘wa'的所有項目
user_list=Wdtest.query.filter(Wdtest.name.contains('wa')).all()
# 模糊查詢
user_list =Wdtest.query.filter(Wdtest.name.like('%a%')).all()
# 查詢名字wa開頭和age為20的所有⽤戶[2種⽅式]
user_list=Wdtest.query.filter(Wdtest.name.startswith('wa'),Wdtest.age == 20).all()
user_list=Wdtest.query.filter(and_(Wdtest.name.startswith('wa'), Wdtest.age == 20)).all()

# 非條件查詢查詢名字不等於wade的所有⽤戶[2種⽅式]
user_list=Wdtest.query.filter(not_(Wdtest.name == 'wade')).all()
user_list=Wdtest.query.filter(Wdtest.name != 'wade').all()

# in 條件查詢
user_list=Wdtest.query.filter(Wdtest.id.in_(['97124f50-0208-11ea-a66c-04ea56212bdf', '3'])).all()

# 所有⽤戶先按年齡從⼩到⼤, 再按id從⼤到⼩排序, 取前5個
user_list=Wdtest.query.order_by(Wdtest.age,Wdtest.id.desc()).limit(5).all()

# 分⻚查詢, 每⻚3個, 查詢第2⻚的數據
pn = Wdtest.query.paginate(2,3)
print(pn.pages)
print(pn.page)
print(pn.items)
4 路由傳參
有時我們需要將同一類 URL 映射到同一個視圖函數處理,比如:使用同一個視圖函數來顯示不同用戶的個人信息。

# 路由傳遞參數
@app.route('/user/<id>')
def user_info(id):
return '%s' % id
路由傳遞的參數默認當做 string 處理

 

####指定請求方式

在 Flask 中,定義一個路由,默認的請求方式為:

GET

OPTIONS

HEAD

在裝飾器添加請求指定方式:

@app.route('/test', methods=['GET', 'POST'])
def test():
return "ok"
5 動態正則匹配路由
flask實現正則匹配步驟:

導入轉換器基類:在 Flask 中,所有的路由的匹配規則都是使用轉換器對象進行記錄

自定義轉換器:自定義類繼承於轉換器基類

添加轉換器到默認的轉換器字典中

使用自定義轉換器實現自定義匹配規則

 

###實現:

導入轉換器基類

from werkzeug.routing import BaseConverter
自定義轉換器

# 自定義正則轉換器
class RegexConverter(BaseConverter):
def __init__(self, url_map, *args):
super(RegexConverter, self).__init__(url_map)
# 將接受的第1個參數當作匹配規則進行保存
self.regex = args[0]
添加轉換器到默認的轉換器字典中,並指定轉換器使用時名字為: re

app = Flask(__name__)

# 將自定義轉換器添加到轉換器字典中,並指定轉換器使用時名字為: regex
app.url_map.converters['regex'] = RegexConverter
使用轉換器去實現自定義匹配規則

當前此處定義的規則是:3位數字

@app.route('/index/<regex("[0-9]{3}"):id>')
def user_info(id):
return "id 為 %s" % id
自定義轉換器其他函數實現

繼承於自定義轉換器之后,還可以實現 to_python 和 to_url 這兩個函數去對匹配參數做進一步處理:

to_python:

該函數參數中的 value 值代表匹配到的值,可輸出進行查看

匹配完成之后,對匹配到的參數作最后一步處理再返回,比如:轉成 int 類型的值再返回:

class RegexConverter(BaseConverter):
def __init__(self, url_map, *args):
super(RegexConverter, self).__init__(url_map)
# 將接受的第1個參數當作匹配規則進行保存
self.regex = args[0]

def to_python(self, value):
return int(value)
系統自帶轉換器

DEFAULT_CONVERTERS = {
'default': UnicodeConverter,
'string': UnicodeConverter,
'any': AnyConverter,
'path': PathConverter,
'int': IntegerConverter,
'float': FloatConverter,
'uuid': UUIDConverter,
}

大哥看到這里累了嗎?如果有時間可以打開微信掃一下看看美圖休息一下,小編個人維護小程序,也算支持一下下小編啦!超低流量在線等您光臨!接下來是一些基礎配置了!


6 增加日志記錄、redis配置加載、mq配置加載
6.1 日志記錄
Settings._init:

# 配置日志
# LOG_LEVEL = "DEBUG"
LOG_LEVEL = "INFO"
日志記錄級別

FATAL/CRITICAL = 致命的,危險的
ERROR = 錯誤
WARNING = 警告
INFO = 信息
DEBUG = 調試
NOTSET = 沒有設置

application._init:

1、日志模塊基礎配置,如:日志存放地址、日志記錄格式、日志等級

#增加日志模塊
def setup_log(Config):
#設置日志等級
logging.basicConfig(level=Config.LOG_LEVEL)
# 創建日志記錄器,指明日志保存的路徑、每個日志文件的最大大小、保存的日志文件個數上限
file_log_handler=RotatingFileHandler('log/log',maxBytes=1024 * 1024 * 300, backupCount=10)
# 創建日志記錄的格式 日志等級 輸入日志信息的文件名 行數 日志信息
formatter = logging.Formatter('%(asctime)s: %(levelname)s %(filename)s:%(lineno)d %(message)s')
# 為剛創建的日志記錄器設置日志記錄格式
file_log_handler.setFormatter(formatter)
# 為全局的日志工具對象(flaskapp使用的)添加日志記錄器
logging.getLogger().addHandler(file_log_handler)
2、日志啟動

#日志啟動
setup_log(Config)
6.2 redis配置及加載
之前我們在config中已經把redis的配置已經寫進去了,所以這里可以直接創redis連接池供app全局使用

 

application._init:

#新增redis連接模塊
def connectRedis(Config):
pool = redis.ConnectionPool(host=Config.REDIS_HOST, port=Config.REDIS_PORT, password=Config.REDIS_PASSWORD,
max_connections=Config.REDIS_POLL)
redis_store = redis.Redis(connection_pool=pool)
return redis_store

使用示例:

@index_blu.route("/redis",methods=["POST","GET"])
def add_toRedis():
logging.info("come to here")
key = request.args.get("key")
application.redis_store.set(key , "1233")
value=application.redis_store.get( key )
print(value)
return "12333"

6.3 rabbitmq基礎配置及加載
# rabbitmq配置訪問
# 添加用戶名和密碼
credentials = pika.PlainCredentials(Config.RABBITUSER, Config.RABBITPASSWORD)
# 配置連接參數
parameters = pika.ConnectionParameters(host=Config.RABBITHOST, port=Config.RABBITPORT, credentials=credentials)
connection = pika.BlockingConnection(parameters)
channel = connection.channel()

使用示例:

@index_blu.route("/rabitmq",methods=["POST","GET"])
def add_rabitmq():
logging.info("come to rabiitmq")
application.channel.queue_declare(queue='queuetest2')

return "33333"

7 全局攔截器配置
filerter.requestFilter

這里只是簡單針對請求路徑非index的進行攔截,如果還有其他攔截條件或者機制,可以繼續在filter這個包下添加

from flask import request
import application

# 攔截器,每次的請求進來都會做的操作
@application.app.before_request
def before_action():
# 獲取當前請求的路由(路徑)
a = request.path
print(a)
u = a.split('/')
if len(a)>2:
if u[1] == 'index':
print('success')
else:
return "無權限請求"

攔截器加載進app:

#攔截器加載
requestFilter.before_action

8 請求對象request和返回對象Response
請求對象request,使用前先導入request模塊

from flask import request
獲取url請求參數:request.args

獲取form表單中的數據:request.form

獲取請求體原始數據:request.data

獲取文件數據:request.files

獲取cookie:request.cookies

獲取header信息:request.headers

獲取請求方法:request.method

獲取請求路徑:request.path

 

Response

視圖函數中可以返回的值

可以直接返回字符串,底層將這個字符串封裝成了Response對象

元組,響應格式(響應體,狀態碼,頭信息),不一定都要寫,底層也是封裝了一個Response對象

返回Response或其子類(jsonify子類返回標准json)

實現一個自定義Response對象步驟

繼承Response對象

實現方法 force_typeforce_type(cls,rv,environ=None)

指定app.response為你定義的類

如果返回的值不是可以返回的對象,就會調用force_type方法

 

實現

class JSONResponse(Response):

@classmethod
def force_type(cls, response, environ=None):
'''
這個方法只有視圖函數返回非字符、非元祖、非Response對象才會調用
:param response:是視圖函數的返回值
:param environ:
:return:
'''
print(response)
print(type(response))
if isinstance(response,(list,dict)):

#jsonify除了將字典轉換成json對象,還將對象包裝成了一個Response對象
response = jsonify(response)

return super(JSONResponse,cls).force_type(response,environ)

app.response_class = JSONResponse

9 異常捕獲及自定義異常
捕獲錯誤

errorhandler 裝飾器

注冊一個錯誤處理程序,當程序拋出指定錯誤狀態碼的時候,就會調用該裝飾器所裝飾的方法

參數:

code_or_exception – HTTP的錯誤狀態碼或指定異常

例如統一處理狀態碼為500,404的錯誤給用戶友好的提示:

@app.errorhandler(500)
def internal_server_error(e):
return '服務器搬家了哈哈哈'

@app.errorhandler(404)
def internal_server_error(e):
return '瞎請求什么路徑呢'

例如自定義錯誤413

@app.errorhandler(413)
def zero_division_error(e):
return '除數不能為0'

異常捕獲

abort 方法

拋出一個給定狀態代碼的 HTTPException 或者 指定響應,例如想要用一個頁面未找到異常來終止請求,你可以調用 abort(404)。

參數:

code – HTTP的錯誤狀態碼

@index_blu.route("/exception",methods=["POST","GET"])
def exception():
logging.info("come to exception")
try:
print(2)
a=3/0
except:
abort(413)
return "ooooo"

10 上下文
上下文:即語境,語意,在程序中可以理解為在代碼執行到某個時刻,根據之前代碼鎖做的操作以及下文即將要執行的邏輯,可以決定在當前時刻下可以使用到的變量,或者可以做的事情。

Flask中有兩種上下文:請求上下文(request context)和應用上下文(application context)。

Flask中上下文對象:相當於一個容器,保存了Flask程序運行過程中的一些信息。

1.application指的是當你調用app = flask(name)創建的這個對象app。 2.request指的是每次http請求發生時,WSGI server(比如gunicorn)調用Flask.call()之后,在Flask對象內部創建的Request對象; 3.application表示用於相應WSGI請求的應用本身,request表示沒出http請求; 4.appliacation的生命周期大於request,一個application存活期間,可能發生多次http請求,所以,也就會有多個request;

請求上下文(request context):在Flask中,可以直接在視圖函數中使用request這個獨享進行獲取先關數據,而request就是請求上下文的對象,保存了當前本次請求的相關數據,請求上線文對象有:request、session

request:封裝了HTTP請求的內容,針對的是http請求。例如:user = request.args.get('user'),獲取的是get請求的參數。

session:用來記錄請求會話中的信息,針對的是用戶信息。例如:session['name'] = user.id 科可以記錄用戶信息。還可以通過session.get('name')獲取用戶信息。

應用上下文(application context):它不是一直存在的,它只是request context中的一個對app的代理,所謂的local proxy。它的作用主要是幫助request獲取當前的應用,它是伴request而生,隨request而滅的。

應用上下文對象有:current_app,g

current_app:應用程序上下文,用於存儲應用程序中的變量,可以通過current_app.name打印當前app的名稱,也可以在current_app中存儲一些變量,例如:

應用的啟動腳本是哪個文件,啟動時指定了哪些參數

加載了哪些配置文件,導入了哪些配置

連接了哪個數據庫

有哪些可以調用的工具類、常量

當前flask應用在哪個機器上,哪個IP上運行,內存多大

current_app.name
current_app.test_value='value'
g變量:g 作為 flask 程序全局的一個臨時變量,充當者中間媒介的作用,我們可以通過它傳遞一些數據,g 保存的是當前請求的全局變量,不同的請求會有不同的全局變量,通過不同的thread id區別

g.name='abc'
注意:不同的請求,會有不同的全局變量

兩者的區別:

請求上下文:保存了客戶端和服務器交互的數據

應用上下文:flask 應用程序運行過程中,保存的一些配置信息,比如程序名、數據庫連接、應用信息等

 

大哥看到這里累了嗎?如果有時間可以打開微信掃一下看看美圖休息一下,小編個人維護小程序,超低流程在線等您光臨!接下來是部署了!


 

11 部署
gunicorn作為服務器,安裝gunicorn

pip3 install gunicorn

啟動

gunicorn -w 3 -b 127.0.0.1:8000 app:app

-w 處理進程數

-b 運⾏主機ip端⼝

dpj.wsgi 項⽬的wsgi

gunicorn常⽤配置

-c CONFIG : CONFIG,配置⽂件的路徑,通過配置⽂件啟動;⽣產環境使⽤;

-b ADDRESS : ADDRESS,ip加端⼝,綁定運⾏的主機;

-w INT, --workers INT:⽤於處理⼯作進程的數量,為正整數,默認為1;

-k STRTING, --worker-class STRTING:要使⽤的⼯作模式,默認為sync異步,可以下載

eventlet和gevent並指定

--threads INT:處理請求的⼯作線程數,使⽤指定數量的線程運⾏每個worker。為正整數,默認為1。

--worker-connections INT:最⼤客戶端並發數量,默認情況下這個值為1000。

--backlog int:未決連接的最⼤數量,即等待服務的客戶的數量。默認2048個,⼀般不修改;

-p FILE, --pid FILE:設置pid⽂件的⽂件名,如果不設置將不會創建pid⽂件

--access-logfile FILE : 要寫⼊的訪問⽇志⽬錄--access-logformat STRING:要寫⼊的訪問⽇志格式

--error-logfile FILE, --log-file FILE : 要寫⼊錯誤⽇志的⽂件⽬錄。

--log-level LEVEL : 錯誤⽇志輸出等級。

--limit-request-line INT : HTTP請求頭的⾏數的最⼤⼤⼩,此參數⽤於限制HTTP請求⾏的允

許⼤⼩,默認情況下,這個值為4094。值是0~8190的數字。

--limit-request-fields INT : 限制HTTP請求中請求頭字段的數量。此字段⽤於限制請求頭字

段的數量以防⽌DDOS攻擊,默認情況下,這個值為100,這個值不能超過32768

--limit-request-field-size INT : 限制HTTP請求中請求頭的⼤⼩,默認情況下這個值為8190

字節。值是⼀個整數或者0,當該值為0時,表示將對請求頭⼤⼩不做限制

-t INT, --timeout INT:超過這么多秒后⼯作將被殺掉,並重新啟動。⼀般設定為30秒;

--daemon: 是否以守護進程啟動,默認false;

--chdir: 在加載應⽤程序之前切換⽬錄;

--graceful-timeout INT:默認情況下,這個值為30,在超時(從接收到重啟信號開始)之后仍然活着

的⼯作將被強⾏殺死;⼀般使⽤默認;

--keep-alive INT:在keep-alive連接上等待請求的秒數,默認情況下值為2。⼀般設定在1~5秒之

間。

--reload:默認為False。此設置⽤於開發,每當應⽤程序發⽣更改時,都會導致⼯作重新啟動。

--spew:打印服務器執⾏過的每⼀條語句,默認False。此選擇為原⼦性的,即要么全部打印,要么全部

不打印;

--check-config :顯示現在的配置,默認值為False,即顯示。

-e ENV, --env ENV: 設置環境變量;
————————————————
版權聲明:本文為CSDN博主「0世界和平0」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/asd529735325/article/details/103011940


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM