2025年后端框架flask学习小记[通俗易懂]

后端框架flask学习小记[通俗易懂]1 写在前面 最近在和几个伙伴尝试搭建一个新闻推荐系统 算是一个推荐算法的实战项目 里面涉及到了前后端交互 该项目里面 使用了 Flask 作为后台框架 为了理清楚整个系统的交互 所以就快速参考着资料学习了下 flask 主要还是参考伙伴们写的 flask 简介和基础的内容 加上了一些其他理解和实验辅助 整理一篇心得文章 算是 flask 初步入门 对于一个算法工程师来讲

1. 写在前面

最近在和几个伙伴尝试搭建一个新闻推荐系统, 算是一个推荐算法的实战项目, 里面涉及到了前后端交互, 该项目里面,使用了Flask作为后台框架, 为了理清楚整个系统的交互,所以就快速参考着资料学习了下flask, 主要还是参考伙伴们写的flask简介和基础的内容, 加上了一些其他理解和实验辅助, 整理一篇心得文章, 算是flask初步入门。

对于一个算法工程师来讲,后端这块虽然不必详细弄清楚原理,但学习一些开发相关知识还是有好处的,因为在实际工作中经常会调试线上的代码调用策略或者模型,我们至少得弄明白,我们的数据流, 模型流到底是怎么走的, 整个系统从输入到最终输出是怎么运行的,这样才能运筹帷幄,从一个更高的角度去看待问题。

好吧,有点扯远了, 本篇文章主要介绍flask,这里依然是从使用的角度整理(因为我对原理也不清楚哈哈), 先不管那么多,会用就行, flask简单的来讲, 就是一个后端框架,基于python语言编写,比较容易上手,或者说不用懂太多细节就能写代码, 把前端传过来的请求,通过编写一些函数进行处理,然后返回给前端。 这里为了更好的理解,我会用一个非常简单的例子贯穿整个流程。 当然,如果想看稍微高大上点的代码,也可以去我们写的fun-rec项目,看新闻推荐系统的代码, 那个是vue-flask交互配合的,更加高级些。

所以,最简单的整个流程就是, 我们在前端页面上输入信息,发送请求给后端(flask), flask根据我们传过来的请求,去找到相应的函数去处理我们的请求(路由), 然后函数处理的结果封装起来返回给前端展示。 这次,主要是看看请求传过来之后,后端这个怎么找函数处理以及返回回去。

主要内容:

先配置环境,安装flask

路由 – 去找函数处理请求

请求、响应和会话

重定向与错误处理

前端简单制作form表单 – 准备交互

介绍两款工具(数据库操作API(sqlarchemy)和接口测试工具(Postman))

小例子打通前后端交互流程

新闻推荐系统vue和flask是怎么联系起来的呢?

Ok, let’s go!

2. 先配置环境,安装flask

这个不用多整理, flask在python里面也是一个包的形式存在,所以我们如果事先安装好了anaconda, 建立了虚拟环境,那么就直接可以

pip install flask

然后输入下面代码测试下:

from flask import Flask
app = Flask(__name__)

@app.route('/') # 这个根目录,就是127.0.0.1:5000进入的位置
def hello_world():
return "hello world"

if __name__ = '__main__':
app.run()

如果正常的话,界面会显示hello world。其实,这就简单的走了一遍小流程(输入网址,根据路由到了hello_word函数,返回结果给前端)。

Flask将(name)作为参数,即Flask在当前模块运行,route()函数是一个装饰器,将请求的url映射到对应的函数上。上述代码将’/’与hello_world()函数进行绑定,因此在请求localhost:5000时,网页显示 Hello World 结果。

这里有几个关键点: 导包, 建立app(Flask(__name__)),路由匹配(@app.route())以及启动(app.run())。 几乎在写每个后端处理之前,这几个先写上再说。

程序的启动是用过Flask类的run()方法在本地启动服务器应用程序

app.run(host, port, debug, options)

# 允许服务器被公开访问
app.run(debug=True, host='0.0.0.0', port=3000, threaded=True)
# 只能被自己的机子访问
# app.run(debug=True, host='127.0.0.1', port=10086, threaded=True)

这几个参数的描述如下:


安装这块比较简单,先这样。

3. 路由 – 去找函数处理请求

web界面输入一个网址,点击回车, 其实是访问的web服务器,然后服务器把结果返回到前端。 这个过程中有个匹配url的过程, 就是flask路由。

Flask中,路由是指用户请求的URL与视图函数之间的映射。Flask通过利用路由表将URL映射到对应的视图函数,根据视图函数的执行结果返回给WSGI服务器。

路由表的内容是由开发者进行填充, 主要有以下两个方式:

route装饰器: 使用Flask应用实例的route装饰器,将一个URL规则绑定到一个视图函数上

# 通过装饰器的方式, Flask框架会将URL规则绑定到test()函数上, 这个很好用
@app.route('/test') # 浏览器访问的时候,输入的url是127.0.0.1/test
def test():
return 'this is response of test function'

add_url_rule() :该方法直接会在路由表中注册映射关系。其实route装饰器内部也是通过调用add_url_rule()方法实现的路由注册

def test():
return 'this is response of test function.'
app.add_url_rule('/test',view_func=test)

我习惯看第一种方式, 感觉比较帅。

3.1 指定HTTP方法

默认情况下, Flask的路由支持HTTP的GET请求, 如果需要试图函数支持HTTP的其他方法, 可以通过methods关键字参数进行设置。 关键字参数methods的类型为list, 可以同时指定多种HTTP方法。

# 接收post和get请求, 如果不指定的话,就是get请求, 此时如果提交post请求是捕捉不到的
@app.route('/user', methods = ['POST', 'GET'])
def get_users():
if request.method == 'GET':
return ... # 返回用户列表
else:
return ... # 创建新用户

这里先说下这两种请求的区别:

GET把参数包含在URL中, 也就是直接输入网址访问, 把参数放到这个网址里面去的时候,访问的就是get请求

POST通过request body传递参数, 采用表单的时候往往就是这个。 关于这个,后面测试接口的时候,会通过Postman进行演示。

这样就完成了最基本的功能, 当然,你说, 这个URL(/user)是写死的目前,如果我不确定怎么办呢? 比如, 我可能这个用户是某某, 而不同的某某,可能有不同的执行操作,这时候,就可以使用动态URL。

3.2 动态URL

动态URL用于当需要将同一类URL映射到同一个视图函数处理,比如,使用同一个视图函数 来显示不同用户的个人信息。那么可以将URL中的可变部分使用一对小括号<>声明为变量, 并为视图函数声明同名的参数:

@app.route('/user/')   # <>提取参数用的
def get_userInfo(uname):
return '%s\'s Informations' % uname


# 输入网址127.0.0.1/user/wuzhongqiang wuzhongqiang's Informations
# 输入网址127.0.0.1/user/zhangsan zhangsan's Informations

除了上述方式来设置参数,还可以在URL参数前添加转换器来转换参数类型: 前端本身默认是传入过来的是字符串格式,如果感觉本身传入的参数不应该是字符串格式的,那就可以在URL参数前添加转换器转换参数类型

@app.route('/user/')   #  int 是一个转换器
def get_userInfo(uname):
return '%s\'s Informations' % uname

使用该方法时,请求的参数必须是属于int类型,否则将会出现404错误。目前支持的参数类型转换器有int, float, string, path。后两者的区别是path里面可以有\。

为了满足一个视图函数可以解决多个问题,因此每个视图函数可以配置多个路由规则

@app.route('/user')
@app.route('/user/')
@app.route('/user/')
def get_userInfo(uname=None):
if uname:
return '%s\'s Informations' % uname
else:
return 'this is all informations of users'

3.3 自定义匹配规则

当然,这里也可以自定义一些规则,去进行输入方面的一些限制, 这时候,就需要继承BaseConverter类,然后写自己的规则了。 这里只是举个简单的例子:

from flask import Flask
from werkzeug.routing import BaseConverter

app = Flask(__name__)

class RegexConverter(BaseConverter):
"""自定义转化器类"""
def __init__(self, url_map, regex):
super(RegexConverter, self).__init__(url_map)
self.regex = regex

def to_python(self, value: str):
return value

# 将自定义的转换器类添加到flask应用中
app.url_map.converters['re'] = RegexConverter

@app.route('/index/')
def index(value):
return "hello, world"

app.run()

# 只有如输入 127.0.0.1/index/123456 1开头,后面5位整数的才能匹配到

这个里面的匹配URL就可以使用正则的形式,匹配非常特殊的那种了。不过,一般用不到这么复杂的。

3.3 URL构建方法

在很多时候,在一个实用的视图中需要指向其他视图的连接,为了防止路径出现问题,我们可以让Flask框架帮我们计算链接URL。简单地给url_for()函数传入一个访问点,它返回将是一个可靠的URL地址

@app.route('/')
def hello():
return 'Hello world!'

@app.route('/test')
def test_url_for():
print(url_for('hello')) # 跳转到了hello函数下面执行

4. 请求、响应和会话

对于一个完整的HTTP请求,包括了来自客户端的请求对象(Request), 服务器端的响应对象(Respose)和会话对象(Session)等。 在Flask框架中,当然也具有这些对象, 这些对象不仅可以在请求函数中使用, 同时也可以在模板中使用。

4.1 请求对象Request

Flask包中, 可以直接引入request对象, 其中包含Form,args,Cookies,files等属性。

Form: 字典对象, 包含表单当中所有参数及值的键和值对

args: 解析字符串的内容, 是问号?之后的URL的一部分, 当使用get请求时, 通过URL传递参数时可以通过args属性获取

cookies: 用来保存cookie名称和值的字典对象

files: 属性和上传文件有关的数据

以一个登陆的例子看看如何搭配属性

from flask import request, session, make_response

@app.route('/login', methods=['POST', 'GET'])
def logion():
if request.method == 'POST':
if request.form['username'] == 'admin':
session['username'] = request.form['username']
response = make_response('Admin login successfully!')
response.set_cookie('login_time', time.strftime('%Y-%m-%d' %H:%M:%S'))
return 'Admin login successfully!'
elif request.method == 'GET':
if request.args.get("username") == 'admin':
session['username'] = request.form['username']
return 'Admin login successfully!'
else:
return 'No such user!'

app.secret_key = '123456'

可以根据method属性判断当前请求的类型,通过form属性可以获取表单信息,并通过session来存储用户登陆信息。当然这里的session,可以换成字典,然后把信息存储到数据库里面去。

由于现在前后端交互会采用json的数据格式进行传输, 因此当前端请求的数据是json类型的时候, 可以使用get_data()方法来获取。

from flask import Flask, jsonify, request
@app.route('/login', methods=["POST"])
def login():
request_str = request.get_data()
request_dict = json.loads(request_str)
# 然后,就可以对request_dict进行处理了,相当于从后端拿到了前端的数据

4.2 响应对象response

如果函数试图想向前端返回数据, 必须是Response对象,主要有下面几种返回数据的格式:

试图函数return多个值

@app.route("/user_one")
def user_one():
return "userInfo.html", "200 Ok", {
"name": "zhangsan"; "age":"20"}

使用Response创建
可以通过直接创建Response对象,配置其参数。

from flask import Response

@app.route("/user_one")
def user_one():
response = Response("user_one")
response.status_code = 200
response.status = "200 ok"
response.data = {
"name": "zhangsan"; "age":"20"}
return response

由于现在前后端交互往往采用的是json的数据格式,因此可以将数据通过 jsonify 函数将其转化成json格式,再通过response对象发送给前端。

当然,这些东西直接这么写,可能会很抽象,后面一个小例子一串就了然, 这里可以先有个印象。

5. 重定向与错误处理

5.1 重定向

当一个请求过来后可能还需要请求另一个视图函数才能达到目的, 就可以调用redirect(location, code=302, Response=None)函数指定重定向页面。

from flask import Flask, redirect, url_for

app = Flask(__name__)

@app.route("/demo")
def demo():
url = url_for("demo2") # 路由反转,根据视图函数名获取路由地址
return redirect(url) # 相当于到了/demo2这个页面,显式this is demo2 page

@app.route("/demo2")
def demo2():
return "this is demo2 page"

@app.route("/")
def index():
# 使用方法:redirect(location, code=302, Response=None)
return redirect("/demo", 301)

url_for函数我理解是能根据给定的url映射到对应的函数,比如给定demo2, 就映射到了demo2(), 但具体执行, 应该是redirect()函数起作用。

5.2 错误处理

当请求或服务器出现错误的时候, 我们希望遇到特定错误代码走不通的处理错误逻辑, 可以使用errorhandler()装饰器

from flask import render_template   # 渲染页面

@app.errorhandler(404)
def page_not_found(error):
return render_template('page_not_found.html'), 404

当遇到404错误时,会调用page_not_found()函数,返回元组数据,第一个元素是”page_not_found.html”的模板页,第二个元素代表错误代码,返回值会自动转成 response 对象。 如果这个地方想在网页里面放张图片的话,一定要放到static目录里面才行, 访问的是静态文件目录, 这个static名字不能改。

这个东西主要是为了后面处理异常,如果满足什么条件,就进行什么样的处理:

from flask import abort
@app.route('/index', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
return render_template('index.html')
if request.method == 'POST':
name = request.form.get('name')
password = request.form.get('password')
if name == 'zhangsan' and password == '123':
return 'login success'
else:
abort(404)
return None

6. 构建form表单,为交互做准备

上面整理了那么一大推, 这里想通过一个例子串一下, 否则总会有一股朦胧之感, 由于我不是很懂前端, 这里就简单参考代码写一个前端页面, 不用很复杂,就构建一个输入用户名和密码的对话框,然后点击提交,看看与后端的交互效果。

前端页面的代码如下:


这个布局方式也是蛮重要的, 就是先建立一个templates目录,这个目录可以认为是有一个模板目录,默认定义了一些前后端交互的代码格式。这个模板是jinjia2(右击目录,mark directory as设置), 然后在该目录下创建一个HTML界面。

然后在上一级目录,创建一个form表单文件,把这个HTML渲染出来:

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/index')
def index():
return render_template('index.html') # 渲染当前的html页面

if __name__ == '__main__':
app.run()

# 输入127.0.0.1:5000/index 就会出来写的那个html页面了,然后输入密码,提交,就会得到一个get请求

此时,就能把前端的html页面显示出来。


当然,比较陋, 但演示足够。下面看看如何交互。

7. 前后端交互小例子

这里前端,从上面的两个框里输入用户名和密码,然后点击提交给后端。 后端接收过来, 把用户和密码封装起来, 给到另一个前端页面, 然后另一个前端页面就能用这个数据了。

首先, 需要先修改上面前端页面数据, 把提交请求的方式改为POST,非常简单, 只需要修改这里。


然后在总目录下建立了request对象.py文件,在这里面写接收数据的逻辑

from flask import Flask, render_template
from flask import request

app = Flask(__name__)

@app.route('/index', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
# 渲染index页面
return render_template('index.html')
elif request.method == 'POST':
# 获取数据
data = {
}
data['name'] = request.args.get('name') # 后面这个name和前端的name保持一致
data['passwd'] = request.args.get('password')

# 返回到前端去
return render_template('index2.html', data=data)

return render_template('index.html')

app.run()

其实也非常简单,输入网址的时候,显式的就是index.html页面,这个页面就是让用户输入用户名密码,然后提交即可,此时由于修改了index的提交方式是post请求,所以后端这块捕捉到,拿到传过来的数据, 给到index2.html, 此时index2.html就可以直接拿到data使用或者用来展示。

index2.html页面此时就能使用data数据了。


框里的这两个,就是index.html传给后端,然后后端传过来的数据, 可以直接在index2.html中显示。 当然,这里的{
{变量名}}的这种定义格式,就是模板事先定义好的,如果不是jinjia2模板,可能不能使用。所谓模板,就是事先定义了一些前后端交互的规则。

上面就是一个前后端交互的小例子啦, 其实flask框架用起来还是比较容易上手的。

8. 介绍两款工具

这里主要是介绍这两天用到的两个工具,SQLAlchemy和Postman。

8.1 SQLAlchemy

这是一个功能强大的python ORM工具包, 也就是提供了API去操作数据库里面的表的相关操作,而不是编写原始的SQL语句,非常方便。安装

# 安装
pip install SQLalchemy
8.1.1 连接数据库

下面创建连接,也就是连接到我们的mysql数据库:

from sqlalchemy import create_engine

def mysql_db(host='127.0.0.1', dbname='3306'):
engine = create_engine("mysql+pymysql://root:123456@{}:49168/{}?charset=utf8".formate(host, dbname))
print(engine) # Engine(mysql+pymysql://root:***@127.0.0.1:49168/3306?charset=utf8)

通过create_engine函数已经创建了Engine,在Engine内部实际上会创建一个Pool(连接池)和Dialect(方言),并且可以发现此时Engine并不会建立连接,只会等到执行到具体的语句时才会连接到数据库。上述代码默认本地已经存在并开启mysql服务。

create_engine("mysql://user:password@hostname/dbname?charset=utf8",
echo=True,
pool_size=8,
pool_recycle=60*30)

第一个参数是和框架表明连接数据库所需的信息,“数据库+数据库连接框架://用户名:密码@IP地址:端口号/数据库名称?连接参数”;echo是设置当前ORM语句是否转化为SQL打印;pool_size是用来设置连接池大小,默认值为5;pool_recycle设置连接失效的时间,超过时间连接池会自动断开。

8.1.2 创建数据库表类

用于SQLAlchemy是对象关系映射,在操作数据库表时是通过操作对象实现的, 每一条记录其实是一个对象,所以需要先创建一个数据库表类说明字段信息。

from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
__tablename__ = 'UserInfo' # 表名
# 数据库字段的类型
index = Column(Integer(), primary_key=True)
user_id = Column(Integer(), unique=True)
username = Column(String(30))
passwd = Column(String(500))

def __init__(self, index, user_id, username, passwd):
self.index = index
self.user_id = user_id
self.username = username
self.paswd = passwd

这个可以当做是固定套路格式, 通过declarative_base()函数,可以将python类和数据库表进行关联映射,并通过 _tablename_ 属性将数据库模型类和表进行管理。其中Column() 表示数据表中的列,Integer()和String()表示数据库的数据类型。

8.1.3 操作数据库

创建完连接, 需要借助sqlarchemy中的session来创建程序与数据库之间的会话,此时通过sessionmarker()函数创建。

def mysql_db(host='127.0.0.1', dbname='test'):
engine = create("mysql+pymysql://root:123456@{}:49168/{}?charset=utf8mb4".format(host,dbname))

session = sessionmaker(bind=engine)
Base.metadata.create_all(engine)
return engine, session()

这样,相当于正式与mysql建立了连接时候的会话。 session常用的方法:

flush: 预提交,提交到数据库文件,此时还未写入数据库文件中

commit: 提交了一个事务

rollback: 回滚

close: 关闭session连接

下面演示下数据库里面的增删改查。

增加数据

# 增加一个用户
engine, session = mysql_db()

user = User("100", "zhangsan", "11111")
session.add(user)
session.commit()

# 也可以通过addall批量提交
engine, session = mysql_db()
user1 = User("101","lisi","11111")
user2 = User("102","wangwu","22222")
session.add_all([user1,user2])
session.commit()

不用我们事先建立数据库和表, 调用程序的时候,会自动建立好。 把用户的信息封装成一个对象,然后采用add的方式就可以添加进去了。 当然session.add()不会直接提交到数据库,当执行了commit()之后才会提交。 这时候,就不用什么insert table_name values …。

查询数据

engine, session = mysql_db()
users = session.query(User).filter_by(passwd='11111').all()

for item in users:
print(item.username,item.passwd)

通过上面代码就可以访问数据库, 类似于select操作了, 其中query()返回一个query对象,在这里指明查哪个大类(这里面映射一个数据表),告诉去哪个表里查。 当然此时并没有真正去查询, 只有等到具体执行函数count(), first(), all()等采取数据库查询。 当然,还可以使用filter()方法指定查询条件,类似where。 这里其实有两种写法:

修改数据

session.query(User).filter_by(username="zhangsan").update({ 
'passwd': "123456"})

删除数据

session.query(User).filter(User.username == "zhangsan-test").delete()
session.commit()

8.2 Postman

当然这个东西由于也是刚接触,并不是太会用。 这个东西是有个接口测试工具, 是为了验证后端开发的接口是否可用。

因为真正开发大项目,前后端是分离开发的, 并且此时前端可能没有完全搭建好,所以接口测试的时候,postman,就相当于一个客户端, 可以模拟用户发起各类的HTTP请求, 将请求数据发送给服务端, 来获取对应的响应结果。 这样就能测试出后端的函数逻辑是否正确。

我这里是偶然接触到,因为学习上面新闻推荐系统的时候,我这边后端的每个py文件都运行通过了,此时想基于界面传数据看看效果,结果就是和前端的vue框架连不起来。 上面我自己写HTML文件好好的, 一旦用上vue框架,再去访问网址总是报错或者被拒绝啥的。

所以,这里就想看看到底是后端给的网址和接口不对,还是前端vue的问题,那么怎么测试呢? 意哥就告诉了我这个工具,用他来模拟前端,给后端发请求,看看后端能返回结果不。

当然具体的下载和使用, 我给出两篇参考文档postman教程, postman教程大全, 这玩意也是个软件,所以直接Windows下载安装即可。

打开之后, 我们新建一个collections,其实就是目录,然后在这里面新建一个request请求,就可以测试了。


我这里给出我这边的测试例子, 我当时想通过postman测试下,能不能访问到后端。测试的后端函数是这个:

@app.route('/recsys/register', methods=["POST"])
def register():
"""用户注册 """
request_str = request.get_data()
request_dict = json.loads(request_str)
print(request_dict)

user = RegisterUser()
user.username = request_dict["username"]
user.passwd = request_dict["passwd"]

# 查询当前用户名是否已经被用过了
result = UserAction().user_is_exist(user, "register")

if result != 0:
return jsonify({
"code": 500, "mgs": "this username is exists"})

#user.userid = snowflake.client.get_guid() # 雪花算法
user.userid = 20211971672
user.age = request_dict["age"]
user.gender = request_dict["gender"]
user.city = request_dict["city"]
print("hello world")

# 添加注册用户
save_res = UserAction().save_user(user)
if not save_res:
return jsonify({
"code": 500, "mgs": "register fail."})

return jsonify({
"code": 200, "msg": "register success."})

用户注册函数, 这是一个post请求格式的,然后需要传入用户的相关参数,给到后端,后端把这个存到用户注册表里面去。然后返回成功信息。

其实逻辑很简单,首先, 建立post请求格式在postman的操作, 首先请求格式改成POST,然后headers这里需要设定json格式。


然后, 在body里面传入请求参数,也就是用户的注册信息, 这里是一个字典的形式


这样,点击右上角send即可发送了。根据下面后端返回的信息,说明后端这块是可以被访问的,没有什么问题。如果想发送get请求,以及传参数,还可以这样:


那,这就确定了, vue框架的配置有问题。

9. 新闻推荐系统vue和flask是怎么联系起来的呢?

这里主要是记录下解决上面这个问题的方法, 因为我这边遇到了vue服务开启完了之后, 输入网址并没有到相应的界面中去,而是报错。 这个问题还是困扰我一段时间的,一开始以为是后端那边的网址不能访问, 但用了postman之后,发现后端这边没问题。

于是我觉得是我vue那边配置有问题,因为我对vue内部一窍不通, 并且我伙伴们之前都测试好了,不可能是代码方面的问题。

于是乎,开始排查路径问题: 这篇文章启发


这个没有问题。下面这里也需要改:


这样操作完了,然后在浏览器输入


这样一顿操作之后,就搞定了上面的问题。202.199.6.190是我实验室服务器的地址。

当然我后端是这样:

总结起来, 就是需要修改前端的main.js里面的网址,然后修改package.json里面的主机地址。 然后访问的时候是从前端running的地址进行访问。

当然,开启前端的过程中还遇到一个奇葩的报错问题:


这个问题我也不知道是啥原因, vue涉及到盲区, 但下面这行代码却能无脑搞定,挺神奇:

# 命令行输入
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p

到这里就差不多了,花了两三天的探索,终于把整个系统换了我实验室服务器跑出来,然后整理这篇文章记录下, 实测能运行, 感兴趣的伙伴可以玩玩啦 😉

项目地址: https://github.com/datawhalechina/fun-rec/tree/master/codes/news_recsys

编程小号
上一篇 2025-02-11 20:27
下一篇 2025-03-02 11:33

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/hz/139374.html