文章目录
区别
flask 轻量级 买房等于买一个毛坯房,什么都要自己弄
django 重量级 买房相当于一个精装修的,但是你不一定会用
mvc: a) Model层:根据业务需求,来封装数据库操作,用于操作相应数据 b) controller层:接收请求,处理业务,返回响应(HTML、JSON、其他) c) view层:由控制层发起模块页面的填充调用
虚拟环境
- 安装虚拟环境
pip install virtulenv
- 下载虚拟环境管理工具,python安装路径不能有中文路径
pip install virtulenvwrapper-win(windows版)
- 创建虚拟环境
- 虚拟环境操作
进入虚拟环境: workon 环境名
退出虚拟环境: deactivate
删除虚拟环境: rmvirtualenv 环境名
列出虚拟环境: lsvirtualenv
进入虚拟环境目录: cdvirtualenv 环境名
查看安装包: pip list
一、flask简介
官网:https://flask.palletsprojects.com/en/2.2.x/
中文学习文档:https://dormousehole.readthedocs.io/en/latest/
- 使用pycharm专业版,可以直接生成flask的框架,专业版的激活码可以上公众号“python联盟”回复“激活码”
- pycharm的community版本,查看文档“python前后端.md”
二、入门
配置讲解
app.py
from flask import Flask
# 使用Flask创建一个app对象,并且传递__name__参数
app = Flask(__name__)
# @app.route,设置访问的url,这里是设置成一个根路径
@app.route('/')
def hello_world(): # put application's code here
return 'Hello World!'
if __name__ == '__main__':
app.run(debug=True,port=5001)
注意:pycharm的debug或者其他配置未生效,见csdn的flask收藏夹
url和视图
- 返回json格式的字符串
from flask import jsonify
- 视图转url
from flask import url_for
-
指定url
-
指定method
在@app.route上,添加method参数,这个参数是一个列表类型,可以传递多个 -
重定向
redirect
模板
- 模板文件,也就是html文件,需要放到templates文件夹下,当然在
Flask(__name__,template_folder)
可以修改模板的地址,一般不修改 - 通过来渲染模板
from flask import render_template
- 可以通过定义为字典来传递变量,通过关键字参数传递
蓝图
from flask import Blueprint
mysql数据库
- SQLAlchemy:是一个独立的ORM框架,可以独立于Flask存在,也可以在其他项目中使用
- Flask-SQLAlchemy:对SQLAlchemy的一个封装,能够更适合在flask中使用
- 安装
pip install pymysql
pip install Flask-SQLAlchemy
- 连接数据库
HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = 'xt_flask'
USERNAME = 'root'
PASSWORD = 'root'
DB_URI = 'mysql+pymysql://{}:{}@{}:{}/{}'.format(USERNAME,PASSWORD,HOSTNAME,PORT,DATABASE)
- 增删改查
#1. 添加数据
# article= Article(title="英语",content="XX")
# ad.session.add(article)
# db.session.commit()
# return "数据操作成功"
# #2. 查询数据
# #filter_by:返回一个类列表的对象
# article= Article.query.filter_by(id=1)[0]
# print(article.title)
# return "数据操作成功"
# #3. 修改数据
# article= Article.query.filter_by(id=1)[0]
# article.content="yyy"
# db.session.commit()
# return "数据操作成功"
# #4. 删除数据
# article= Article.query.filter_by(id=1).delete()
# db.session.commit()
return "数据操作成功"
迁移migrate
- ModuleNotFoundError: No module named ‘flask_mail’ 错误解决
使用pip3 再安装一遍
terminal操作
1. flask db init #初始化
2. flask db migrate -m "修改的信息" #提交类似于commit
3. flask db upgrade #更新到数据库 #类似于push
邮件
pip install flask-Mail
@bp.route("/mail")
def senf_mail():
message=Message(
subject="邮箱测试",
recipients=["409788696@qq.com"],
body="这是测试邮件",
sender="maohui@well-healthcare.com"
)
mail.send(message)
return "success"
CORS跨域问题
from flask_cors import CORS
CORS(app,supports_credentials=True) #解决跨域问题
部署flask项目
1. docker search python
2. docker pull python:3.10
3. docker run -uroot -it -p 3344:5000 python:3.10 /bin/bash
4. pip freeze > requirements.txt
5.
将项目文件移到linux中
6. 见mysql笔记,另外搭建一个MySQL的linux独立docker 镜像环境(root:123456)
7. aiyun开放mysql端口安全组
8. 安装git,并clone下项目文件
9. pip install -r requirements.txt(解决pip源太慢,sudo pip config set global.index-url https://pypi.doubanio.com/simple)
**注意此时用的是一个python自带的开发者服务器,无法支持生产环境的并发,所以需要用到nginx服务器**
nginx介绍
官网:http://nginx.org/
中文资料:http://www.nginx.cn/doc/index.html
http://tengine.taobao.org/book/
简介
- 轻量级
- 并发能力强 最高50000
- 高度模块化
- 负载均衡
- 反向代理
·正向代理:
·反向代理:
·总结
- 下载安装使用
下载安装使用
- 下载安装见官网
- ngnix控制
- 配置文件
uwsgi介绍
pip install uwsgi
部署
blog项目
环境
pip镜像
在当前登录用户的主目录下创建pip目录,并创建pip.ini文件,使用豆瓣网的python镜像,编辑文件为如下内容
[global]
index-url = http://pypi.doubanio.com/simple/
[install]
trusted-host = pypi.doubanio.com
需要安装的库
pip install
pymysql
SQLAlchemy #mysql的ORM库
jinja2 #flaskde mo模板引擎
Flask #flask框架核心应用
flask-cors #flask的跨域解决方案
redis #Redis缓存服务器处理库
pillow #python图像处理库
requests
jieba #python中文分词库
Whoosh #python用于创建倒排索引的库
blinker #支持库,用于flask的对象通信
flask-msearch #基于flask和sqlalchemy的全文搜索库
前端页面开发
- ui设计
- 绘制线框图
- 使用html实现线框图
- 填充页面内容
- 对内容进行微调
数据库开发设计
1.按照业务需求,设计表名和字段名
例如:
Flask框架基础
- 路由与参数
/
/int:args
参数必须作为函数的形参
def fun(args)
return f"{args}"
读取get的参数:
value=request.args.get("")
读取post的参数:
value=request.form.get("")
- RESTful
- 重定向
1)使用flask自带的
@app.route('/red')
def func():
return redirect(url_for('index'))
2)使用js重定向
@app.route('/red')
def func():
html = '感谢访问,2秒后将跳转到首页'
html += "<script>"
html += "setTimeout(function(){location.href='/'},2000);"
html += "</script>"
return html
- Session 和Cookie
1)要处理session,则必须要为app实例设置SECRET_KEY配置参数,配置随机数生成器(Session ID),再使用session函数进行操作.
2)要处理Cookie,需要使用response对象往HTTP的响应中写入满足HTTP协议的Cookie要求的信息(key,Value,Age)
设置session
@app.route('/session)
def sess():
session['username'] = 'maohui'
session['role'] = 'editor'
return Done
设置cookie
@app.route('/cookie)
def cookie():
response = make_response("这是设置cookie的操作")
response.set_cookie('username',mao,max_age=30)
response.set_cookie('pwd',123456,max_age=30)
return response
读取cookie和session
@app.route('/read)
def read():
return "您当前的昵称是%s" %session.get('username')
return "%s"request.cookies.get('username')
session写在后台,可以在一个函数中读写。
cookie无法在同一个接口中写入后马上获取。
flask框架核心
- 拦截器:对接口请求进行预先处理,然后处理交由控制器。
1)全局拦截器,应用于flask实例(app)中,对所有经过当前系统的请求进行拦截检查。
a)全局拦截器,要设置好白名单,
2)模块拦截器,只针对某一种模块进行拦截,应用于Blueprint模块中
#全局拦截器
白名单:
1.所有的静态资源(JS,image,css等)必须设置为白名单
2. 登录或者注册等不需要拦截的页面,或会影响权限操作的页面
根据业务需求,也可以设置黑名单
@app.before_request
def defore_request():
user_id = session.get("user_id")
if user_id:
try:
user = UserModel.query.get(user_id)
# 给g绑定一个叫做user的变量,他的值是user这个变量
# setattr(g,"user",user)
g.user = user
except:
g.user = None
pass_list = ['/','/reg','/login']
suffix = url.endswith('.png') or url.endswith('.jpg') or url.endswith('.css') or url.endswith('.js')
if url in pass_list or suffix:
pass
else:
#开始拦截
#模块拦截器
写在蓝图中,只对蓝图中的控制器起拦截作用
@bp.before_request
def defore_request():
url = request.path #读取到当前接口的地址
if url == '/session':
PASS
elif session.get('islogin') != 'true':
return "未登录"
else:
PASS
jinja2模板引擎
- 语法
(1){%…%}用于循环或者判断语句,赋值等等
判断:
{%if ....%}
{%else%}
{%endif%}
循环:
{%for i in range(10)%}
{%endfor%}
赋值:
{% set loop=100/10%} {# loop的值为10.0,是float类型#}
{% set intloop = loop | int%} {# 使用过滤器将loop强制转换为int#}
(2){{…}}用于表达式的值的引用
(3){#…#}用于模板引擎的注释,如果注释中存在模板引擎的语法,那么使用
将不被模板引擎认为是注释,注释中的语法将被执行,此时用{#…#}进行注释。
- 定义一个函数,供jinja2调用
1)第一种文案,使用上下文处理器来注册自定义函数到jinja2模板引擎中,并且返回一个字典类型的数据
@jinja2.context_processor
def fun():
type={'1':'learning_notes','2':'postgraduate'}
return dict(mytype=type)
前端调用的是mytype,于函数名无关
2)第二种方法,按照标准的函数调用的方式进行,需要放到app中
def fun1():
type={'1':'learning_notes','2':'postgraduate'}
return type
app.jinja2.env.globals.update(mytype=fun1)
app.jinja_env.filters.update(mytruncate=mytruncate)
- 如果要为自定义的函数传参,则需要使用二层闭包进行包裹
@jinja2.context_processor
def fun():
def myfunc(args):
type={'1':'learning_notes','2':'postgraduate'}
return dict(mytype=myfunc)
- 模板的继承
1)在母版中,后续需要插入内容的位置插入
{% block content%}
{% endblock%}
2)在需要导入母版的页面写入
{% extends 'base.html'%} {# 当前页面继承至base.html母版#}
{% block content %}
.....
{% endblock %}
3)按需引入side.html,在需要的页面引入
{% include 'side.html' %}
- 错误页面
@app.errorhandler(500)
def func(e):
return render_template('error-500.html')
@app.errorhandler(404)
def func(e):
return render_template('error-.html')
数据库
1.pymysql+python魔术方法(dict,
setattr,getattribute)
2. ORM:
a) Object-Relational Mapping:对象-关系映射。把数据转换成python对象。进而实现数据库的操作对象化。
b) 数据库中的表->python的类
c)表里面的列->类的属性
d)表里面的行->类的实例,字典对象表述
e)字典对象的Key对应列,Value对应值
f)对增删改查进行封装
flask框架本身是最小化web服务内核,表与表之间的关系,不一定要定义在数据库中,心中有关系就行。(数据库为了维护主外键关系,会增加额外消耗)
。
删除数据时,尽量使用软删除(设置标识),而不是直接硬删除(Delete from-会索引重建)。
SQLAlchemy
- 查询
.first() -> 直接返回一行数据对象
.all() -> 直接返回包含多条数据对象的列表
.filter_by(x=y,a=b) -> 只适用于等值查询,其参数为字典参数的传值方式
.filter() -> 适用于复杂查询条件的对比,其参数为条件运算
查询过程中,可以使用db.session的方式进行查询(支持多表),也可以使用model. query的方式进行查询(不支持多表),所以优先使用db.session。
基础查询汇总,直接打印一个类的时候,具体打印什么内容,由类的__repr__魔术方法决定,可以重写
```python
# select * from users
result = dbsession.query(Users).all()
# select userid, username from users
result = dbsession.query(Users.userid,Users.username).all()
# select * from users where userid=1 and qq='12345678'
result = dbsession.query(Users).filter_by(userid=1,qq='12345678').all()
# select * from users where userid>7 or nickname='丹尼'
result = dbsession.query(Users).filter(or_(userid>7 , users.nickname=='丹尼')).all()
# select * from users limit 3
result = dbsession.query(Users).limit(3).all()
# select * from users limit 3,5
result = dbsession.query(Users).limit(5).offset(3).all()
# select count(*) from users where ....
count= dbsession.query(Users).filter(User.userid > 3).count()
# select distinct (qq) from users
result= dbsession.query(Users.qq).distinct(Users.qq).all()
# select * from users order by userid desc
result= dbsession.query(Users).order_by(Users.userid.desc()).all()
# select * from users where username like '%qiang%'
count= dbsession.query(Users).filter(User.username.like('%qiang%')).all()
# select * from users group by role
result= dbsession.query(Users).group_by(User.role).all()
result= dbsession.query(Users).group_by(User.role).having(Users.userid>2).all()
# 聚合函数:min,max,avg,sum
# select sum(credit) from users
result = dbsession.query(func.sum(Users.credit)).first()
# filter : == >= > <= < ! in not
- SQLAlchemy连接查询和其他
- 内连接
# select * from article inner join users on article.userid=user.userid where article.articleid=1
# 多表连接查询时,返回的结果集不再是单纯的[Model,Model]数据结构,而是每张表的结果由独立的对象来维护
result = dbsession.query(Article,Users).join(Users,Article.userid == Users.userid).filter(Article.article==1).all()
result= dbsession.query(Article,articleid,Users.nickname).join(Users,Article.userid == Users.userid).filter(Article.article==1).all()
- 外连接
# 查询每一个用户发表过的文章的阅读总量,outerjoin默认为左外连接
# select users.userid,user.nickname,sum(article.readcount) as total from users left join article on users.userid= article.article.userid group by (users.userid)
result = dbsession.query(Users.userid,Users.nickname,func.sum(Article.readcount)).outerjoin(Article,Users.userid==Artilce.userid).group_by(Users.userid).all()
- 复杂查询:and和or混用,username like ‘qiang’ or (userid>3 and nickname=‘reader3’)
result = dbsession.query(Users).filter(or_(users.username.like('%qiang%'),and_(Users.userid>3,Users.nickname=='reader3'))).all()
result = dbsession.query(Users).filter(and_(Users.username.like('%qiang%'),or_(Users.userid>3,Users.nickname=='reader3'))).all()
result = dbsession.query(Users).filter(Users.username.like('%qiang%'),or_(Users.userid>3,Users.nickname=='reader3')).all()
- 三表连接
result = dbsession.query(Comment,users).join(Users,Comment.userid==Users.userid.join(Article,Article.articleid==Comment.articleid).all()
- 利用SQLALchemy执行原生sql
result= dbsession.execute("select * from users where userid>5").fetchall()
- SQLAlchemy与JSON
JSON:JavaScript Object notation 是javascript的内置数据格式,由javascript的数组、对象构成
[{},{},{}]
{[],[],[]}
{{},{},{}}
JavaScript的数组 --> python列表是完全一致的定义方式
javascript的对象 --> python的字典是完全一致的定义{key:value,key:value}
实现首页内容填充
。。。。。。
- 重写truncate过滤器
1.修改源码,修改jinja2的truncate的源代码
- 自定义一个函数,并在jinjia模板中注册
- 利用JavaScript处理json的方式进行原生代码的前端渲染
a) 通过后台:原生python输出html,使用模板引擎。浏览器直接绘制html。
b)通过前端:使用JavaScript动态输出填充DOM元素(json)。对搜索引擎不友好。前后端分离(web app主流开发模式) -> 核心思想:字符串拼接。
c) 前后端分离可以有效减少服务器渲染html的资源消耗,把渲染的过程交给前端浏览器处理。 - 利用vue进行前端渲染
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<ul id="recommended_article">
<li v-for="(article,index) in content">
<a v-bind:href="'/article/'+article[0]">${index}}. ${article[1].substr(0,4)}}</a>
</li>
</ul>
<!--
1. 填充时绑定JSON数据,进行类似于jinja2的填充方式
2. 循环:v-for 来指定循环,写在需要循环的标签语句内
-->
var v = new Vue({
el: '#recommended_article', //绑定的元素不能与v-for同级,最好上级
data: {"key" : "value"},
delimiters: ['${' , '}}'] //自定义分隔符,左边和右边
});
登录和注册
- 图片验证码和邮箱验证码
- 验证码:图片验证码,邮箱验证码,短信验证码
- 实现原理:前端输入-》后台接收地址进而生成随机验证码(同时将该验证码保存起来:session,文件或数据库,缓存服务器)-》返回给用户-》用户填写并提交到后台-》后台进行对比校验
- 验证码的作用:
a)防止自动化代码(爬虫、恶意脚本)来直接发送请求
b)确认用户信息的真是性:邮箱地址,手机号码 - 图片验证码的功能实现:
a)绘制基础的图片,使用pillow将随机验证码文本绘制成一张图片。字体,变形,干扰线
b)将验证码图片响应给前端,供前端用户识别并提交给服务器
c)校验。成功,则执行后续代码,否则,用户重新提交
切花验证码图片,欺骗浏览器url变化,重新请求一个地址
onclick="this.src='/user/vcode?'+Math.random()"
- 邮件验证码:
a) 需要支持邮件发送或者接收的模块
b)需要一台
- 用户注册
a) 向users表插入一条记录
b) 新用户,赠送50积分
c) 向credit表插入一条积分详情
d) 用户注册成功直接保持登录状态
添加一个回车事件响应,当返回的事件为null时执行下面的代码,当返回事件等于为enter时执行下面代码。
function doReg(e) {
//回车事件
if(e != null && e.keyCode !=13){
return false;
}
var regname = $.trim($("#regname").val());
var regpass = $.trim($("#regpass").val());
var regcode = $.trim($("#regcode").val());
<button type="button" class="btn btn-primary" onclick="doReg(null)">注册</button>
<input type="text" id="regcode" class="form-control col-4"
placeholder="请输入邮箱验证码,不区分大小写" onkeyup="doReg(event)">
-
用户登录
-
自动登录
a)利用cookie的持久化存储机制来保存用户登录信息
i.session ID
ii. 利用利用加密机制,存储一个自定义规则的GUID
iii. 直接保存username 和password(MD5),直接将cookie发送给服务器- 在登录成功后,将cookie写入浏览器
- 在接口中,从cookie中获取用户名和密码,并完成登录验证
b)利用全局拦截器实现自动登录的处理
文章
页面静态化处理技术
1.优化网络
2.优化硬盘
3.优化CPU:CPU是系统中容易出现
-
静态化:HTML(JS+CSS) -> 动态生成内容(处理)->模板引擎 -> 消耗服务器资源。
-
如何将动态内容静态化
- 预处理:直接把数据库查询和模板渲染的过程先实现一遍,但是不响应,而是直接将渲染完成后的页面写入HTML文件中
- 当访问首页时,直接读取首页对应的HTML页面文件。
- 如果访问第二页时,则同样地读取第2页对应的HTML文件。
- 页面没有更新?即使增加了新文章,也无法即使反馈给前端
a)定时触发更新 b)新增时触发更新:在新增接口中,直接删除已有静态页面。 c)用户访问时触发更新:用户访问时,先判断是否存在静态页面,存在直接响应,否则先查询、渲染后再响应,并同时生成静态页面。 d)手动删除策略
5.伪静态:通过路由规则的定义,来模拟一个.html后续的URL地址,让用户或搜索引擎误以为这是一个HTML静态页面。对性能提升没有一点作用
Redis
Redis官网:https://redis.io/
windows下载地址:https://github.com/MicrosoftArchive/redis/releases
redis命令参考:http://doc.redisfans.com/
- 什么是缓存:高速设备代替低速设备承载更多处理能力和IO操作。
- Redis是一个内存型数据库,用于缓存硬盘数据,进而提示性能
- 为什么需要缓存:让内存代替硬盘去做更多的事,内存比硬盘快至少100倍,通过缓存可以更好支撑高并发。
- Redis等内存型数据库通常事“非关系型数据库”,以key-value存储
- redis默认有16个数据库,[0-15],默认使用0
数据类型
数据类型 | Key | Value | 注意事项 |
---|---|---|---|
字符串(String) | username password | maohui 123456 | Redis没有数字类型,归为字符串类型中 |
哈希(Hash) | article | Key:article Value:后台框架调优 | Hash类型的值本身又是一个键值对的字典类型 |
列表(List) | headline | Flask的路由规则,RESTful接口规范 | 列表中的值可以重复,也可以存JSON数据,也可以存JSON数据 |
集合(Sets) | phone | 213,567 | 集合的用法与列表类似,只是存的值不允许重复 |
有序集合(Sorted Sets) | phone | 123,456 | 有序集合也称ZSet,当值写入后将会进行排序后保存 |
下载安装
- windows
1.进入github地址进行安装
2. 进入redis目录,启动服务端
redis-server.exe redis.windows.conf
3. 再打开一个命令窗口,启动客户端
redis-cli
4. 可视化工具:Redis Desktop Manager
ubuntu指定文件启动,将文件中的daemonize yes,
sudo redis-server /etc/redis/redis.conf
Redis持久化
将数据从redis保存到磁盘中,因为不保存,重启redis的话,数据会丢失。
- RDB持久化方式能够在指定的时间间隔内对数据进行快照存储,这也是redis默认的持久化策略。
- AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据。默认关闭,开启:appendonly 改为yes。
python操作Redis
原生使用tcp/ip传输,直接用socker就可以发
关于缓存更新的策略思考
- 从数据库的表中全部把数据一次性保存到缓存中,然后从缓存中去取
- 按需存取:要去读取一条数据,优先从Redis取,如果Redis里面没有,则再向Mysql中取(如果数据是第一次取,则必然会从数据库中去取),取得同时,同步将该条数据缓存到Redis中。(第二次取值时,则会从Redis中取到,并且Redis也进行了更新)
- Redis的Key如何设计?
- 使用路由地址+参数 是否可以作为Key?比如 redis_page_15或login-username-maohui@qq.com
- 使用 函数和方法名 +参数作为Key? paginate_redis_15。
- 使用任意表格当中的列作为Key
- 使用表格当中的多个列合并为Key
- 在同一个系统中,存在Redis数据源和MySQL数据源时,对于数据的操作,还有一些可能出现的异常情况
- 雪崩:集群,哨兵、消息队列
- 如果Redis的内存已经存满了:数据的情况下,如何处理?LRU 最近最少使用算法。
- 后台架构(架构师)
- 高可用。7*24小时不间断服务。集群
- 高并发。
- 系统瓶颈的相对比例。
- 高性能。高并发,正常并发,长时间运行。。。。。。
- 安全性(Security)。
- 针对方案进行专题测试并进行优化
部署Blog
参考博客:
部署:https://blog.csdn.net/weixin_42118531/article/details/106592752
python虚拟环境:https://blog.csdn.net/weixin_39860919/article/details/110703314
- linux操作系统
- python运行环境
- git环境
- 安装运行uwsgi,创建uwsgi.ini以及uwsgi.py
pip3 install uwsgi
安装就可以了。(uwsgi必须安装在系统级别的Python环境中,不要安装到虚拟环境中)。然后创建一个叫做uwsgi.ini的配置文件,uwsgi.py文件替代app.py
启动执行uwsgi.ini文件,同时项目也是启动状态:
uwsgi --ini uwsgi.ini
- 安装ngnix
优点:- uwsgi对静态文件资源处理并不好,包括响应速度,缓存等。
- nginx作为专业的web服务器,暴露在公网上会比uwsgi更加安全一点。
- 运维起来更加方便。比如要将某些IP写入黑名单,nginx可以非常方便的写进去。而uwsgi可能还要写一大段代码才能实现。
1. 安装:
sudo apt-get install nginx
# nginx简单操作命令
启动:sudo systemctl start nginx
关闭:sudo systemctl stop nginx
重启:sudo systemctl restart nginx
2. 添加配置文件
sudo vim /etc/nginx/conf.d/blog.conf
sudo nginx -t
如果不报错,说明成功。 每次修改完了配置文件,都要记得运行
systemctl start nginx
# /etc/nginx/nginx.conf
sudo vim /etc/nginx/nginx.conf
# xxx是本机的root权限的用户名
user xxx;
# 重启nginx
systemctl restart nginx
# 运行项目
uwsgi --ini uwsgi.ini
部署遇到的问题:
- 成功解决
OSError cannot open resource
原因:其中用到的字体 simsun 在 Ubuntu 系统中可能并没有,怎么办呢?其实也很容易,我看到好多博客都是说 Windows 系统怎么解决的,在 Windows 系统下,可以进入 C:\Windows\Fonts 目录下,把里面的可用字体的路径复制到程序中的调用部分就可以了。
受此启发,只要也在 Linux 系统中,找到系统自带的字体,然后把路径改了就行。
在 Linux 下,查看系统字体路径的命令为
fc-list
使用 /usr/share/fonts/truetype/dejavu中的字体,或者把要用的字体下载到此文件夹下就可以了
例如:DejaVuSans.ttf
- 在app.py的文件中,在if name ="main"下导入注册的蓝图时,无法访问蓝图路由,error“404”
if __name__ == '__main__':
# 将注册蓝图放在这里,解决循环导入问题(或者将蓝图中导入数据库模块的操作放在函数中)
from apps import * # 导入init中的蓝图
app.register_blueprint(index_bp) # 注册蓝图
app.register_blueprint(user_bp) # 注册蓝图
app.register_blueprint(article_bp)
app.register_blueprint(post_bp)
app.run(debug=True, host='0.0.0.0', port=3355)
You are calling your app.register_blueprint in a function that will never be seen by the wsgi server.
I’m guessing you have followed some kind of guide that makes these nice functionsfor you to configure/initialise etc the app. But it is purely made for the development server it would seem.
You have to call Blueprint and init_app outside the functions, or create a new wsgi.py file that imports app and then runs the Blueprint and api.init_app commands.