复制成功

RSS源订阅说明页面

本页面提供说明本站RSS源订阅的一些基本功能,订阅用户可以操作下面的控件查看RSS源订阅效果。本站提供了所有文章RSS源订阅功能, 同时也支持单个类别文章RSS源订阅,具体RSS-FEED请操作下面的组件获取。

正在加载请稍后...

订阅链接:

<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <id>58</id>
  <title>Blogin</title>
  <updated>2023-09-27T02:08:19.928278+00:00</updated>
  <link href="https://2dogz.cn"/>
  <generator uri="https://lkiesow.github.io/python-feedgen" version="0.9.0">python-feedgen</generator>
  <subtitle>Blogin是一个个人博客网站,后端使用Flask框架,前端使用Bootstrap4,主要分享一些编程类的技术以及一些陈词滥调的文章!</subtitle>
  <entry>
    <id>60</id>
    <title>[Git]Gerrit接口使用说明</title>
    <updated>2023-09-27T02:08:19.930028+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;p&gt;使用Gerrit的REST API默认是&lt;code&gt;匿名用户&lt;/code&gt;的权限,如果需要鉴权可以通过下面的方式实现,比如访问&lt;code&gt;/project/&lt;/code&gt;需要鉴权在前面加上&lt;code&gt;/a/&lt;/code&gt;就可以了,访问鉴权&lt;code&gt;/a/projects/&lt;/code&gt;接口&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;1.&lt;strong&gt; 获取Gerrit的Token&lt;/strong&gt;&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在用户设置界面的&lt;code&gt;HTTPCredentials&lt;/code&gt;, 如下图&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/4623output.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;点击&lt;code&gt;GENERATE NEW PASSWORD&lt;/code&gt;会弹出包含有&lt;code&gt;Token&lt;/code&gt;的弹框,如下图&lt;code&gt;New Password&lt;/code&gt;后面的就是 REST API的鉴权Token&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/6254output%20%281%29.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;2&lt;strong&gt;. 使用HTTP Basic鉴权&lt;/strong&gt;&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;Gerrit使用的&lt;code&gt;HTTP Basic&lt;/code&gt;的方式进行鉴权,在1.1获取Token后可以通过下面的方式去访问REST API接口&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;命令行&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;curl --user admin:token https://gerrit.cn/a/projects/&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;python示例&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;import requests &#13;
url = f'http://172.20.107.233:8888/a/projects/' &#13;
res = requests.get(url, auth=('admin', token)) &#13;
print(res.text)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;上面的&lt;code&gt;token&lt;/code&gt;替换成上面获取的实际Token,Gerrit的地址替换成真实地址!&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;正常的话,会返回下面的响应内容&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-json"&gt;)]}'&#13;
{&#13;
  "All-Projects": {&#13;
    "id": "All-Projects",&#13;
    "state": "ACTIVE",&#13;
    "web_links": [&#13;
      {&#13;
        "name": "browse",&#13;
        "url": "/plugins/gitiles/All-Projects"&#13;
      }&#13;
    ]&#13;
  },&#13;
  "All-Users": {&#13;
    "id": "All-Users",&#13;
    "state": "ACTIVE",&#13;
    "web_links": [&#13;
      {&#13;
        "name": "browse",&#13;
        "url": "/plugins/gitiles/All-Users"&#13;
      }&#13;
    ]&#13;
  },&#13;
  "hello": {&#13;
    "id": "hello",&#13;
    "state": "ACTIVE",&#13;
    "web_links": [&#13;
      {&#13;
        "name": "browse",&#13;
        "url": "/plugins/gitiles/hello"&#13;
      }&#13;
    ]&#13;
  }&#13;
}&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;需要注意的是,为了防止&lt;code&gt;XSSI(XSSICross Site Script Inclusion)&lt;/code&gt;攻击,JSON响应数据前面包含了&lt;code&gt;)]}&amp;#39;&lt;/code&gt;字符,在后续的数据处理中需要将其删除。&lt;/blockquote&gt;&#13;
&#13;
&lt;h1&gt;&lt;strong&gt;3. 使用pygerrit2库访问Gerrit接口&lt;/strong&gt;&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;由于Gerrit没有像python-gitlab那样的库,但是可以使用&lt;code&gt;pygerrit2&lt;/code&gt;库来简化对Gerrit平台的API调用&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;pip install pygerrit2&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;简单的使用示例&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from pygerrit2 import HTTPBasicAuth, GerritRestAPI&#13;
&#13;
class GerritAPI:&#13;
    def __init__(self, url, username, password):&#13;
        self.gt = GerritRestAPI(url, auth=self.auth(username, password))&#13;
&#13;
    def auth(self, username, password):&#13;
        return HTTPBasicAuth(username, password)&#13;
&#13;
    def get_all_projects(self):&#13;
        projects = self.gt.get('/projects/')&#13;
        return projects&#13;
 &#13;
kgt = GerritAPI(&#13;
    url='http://172.20.107.241:8080',&#13;
    username='admin',&#13;
    password='替换为gerrit用户的密码'&#13;
)&#13;
&#13;
# 获取授权用户所能查看的所有仓库列表&#13;
print(kgt.get_all_projects())&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;虽然使用&lt;code&gt;requests&lt;/code&gt;或者自带的&lt;code&gt;urllib&lt;/code&gt;库也可以访问&lt;code&gt;Gerrit&lt;/code&gt;的API接口,但是使用&lt;code&gt;pygerrit2&lt;/code&gt;库可以简化我们的操作,比如&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;对&lt;code&gt;Gerrit&lt;/code&gt;接口返回数据的预处理&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;默认添加了授权,不需要在接口前面增加&lt;code&gt;/a&lt;/code&gt;&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/60/" rel="alternate"/>
  </entry>
  <entry>
    <id>59</id>
    <title>[Python]Flask-SQLAlchemy一些高级用法记录(2)</title>
    <updated>2023-09-27T02:08:19.930001+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="1. 使用Mixin来定义普通公共方法" name="1. 使用Mixin来定义普通公共方法"&gt;&lt;/a&gt;1. 使用Mixin来定义普通公共方法&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在&lt;a href="https://2dogz.cn/blog/article/57/"&gt;上一节&lt;/a&gt;中介绍了使用Mixin来定义公共字段,在最近的工作中,除了可以定义普通的公共字段之外还可以通过继承的方式定义普通的功能方法,如下面的代码示例&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from sqlalchemy.ext.declarative import declared_attr&#13;
&#13;
class BaseColumn:&#13;
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)&#13;
&#13;
    def save(self):&#13;
        db.session.add(self)&#13;
        db.session.commit()&#13;
&#13;
    def delete(self):&#13;
        db.session.delete(self)&#13;
        db.session.commit()&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;定义上述类之后,之后在定义新的类模型时,就可以通过继承的方式去继承公共属性以及公共方法,如下示例&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class Music(BaseColumn, db.Model):&#13;
    __tablename__ = 't_music'&#13;
&#13;
    name = db.Column(db.String(512), default='', comment='歌曲名称')&#13;
    author = db.Column(db.String(512), default='', comment='歌手名')&#13;
&#13;
&#13;
class Video(BaseColumn, db.Model):&#13;
    __tablename__ = 't_video'&#13;
&#13;
    name = db.Column(db.String(512), default='', comment='电影名称')&#13;
    director = db.Column(db.String(512), default='', comment='电影导演')&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;通过类对象的方式继承,在子类中字段都会被自动放到最前面,如果想放到子类字段的后面,参考上一节的内容!&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;&lt;span class="marker"&gt;t_music&lt;/span&gt;、&lt;span class="marker"&gt;t_vedio&lt;/span&gt;表结构如下图所示&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/6849image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在&lt;span class="marker"&gt;BaseColumn&lt;/span&gt;中还定义了两个共有方法,分别是&lt;span class="marker"&gt;save&lt;/span&gt;、&lt;span class="marker"&gt;delete&lt;/span&gt;,在对数据进行增删改查的时候,就可以直接通过模型类实例来调用这两个方法,实现数据的存储以及删除了,实例代码片段如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;# 插入数据&#13;
video = Video(name='让子弹飞',director='姜文')&#13;
video.save()&#13;
&#13;
# 删除数据&#13;
v = Video.query.get(id)&#13;
v.delete()&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="2. 定义类方法" name="2. 定义类方法"&gt;&lt;/a&gt;2. 定义类方法&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;除了上述的实例方法之外,还可以定义类方法。在使用flask-sqlalchemy的进行查询的时候一般都是ModelName.query的方式去进行查询的,下面示例代码片段演示了如何定义一个&lt;span class="marker"&gt;update_or_insert&lt;/span&gt;的类方法&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class BaseColumn:&#13;
    @classmethod&#13;
    def update_or_insert(cls, condition: tuple, **kwargs):&#13;
        """&#13;
        更新已有数据或者插入新的数据&#13;
&#13;
        :param condition: 查询条件&#13;
        :param kwargs: 需要更新或者插入的字段&#13;
        :return: 类实例对象&#13;
        """&#13;
        existed = cls.query.filter(*condition)&#13;
        if existed.first():&#13;
            existed.update(kwargs)&#13;
            existed = existed.first()&#13;
        else:&#13;
            existed = cls(**kwargs)&#13;
            db.session.add(existed)&#13;
        db.session.commit()&#13;
        return existed&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;通过&lt;span class="marker"&gt;@classmethod&lt;/span&gt;装饰器将&lt;span class="marker"&gt;update_or_insert&lt;/span&gt;变成类方法,在子类查询的时候就可以通过下面的示例代码片段进行使用了&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;video = Video.update_or_insert(Video.name == name, name=name, director=director)&#13;
video.save()&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;完整的示例代码可以查询github仓库,仓库地址:&lt;a href="https://github.com/weijiang1994/blog-demo/tree/main/orm_feature"&gt;https://github.com/weijiang1994/blog-demo/tree/main/orm_feature&lt;/a&gt;&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/59/" rel="alternate"/>
  </entry>
  <entry>
    <id>58</id>
    <title>[陈词滥调]2022年度总结</title>
    <updated>2023-09-27T02:08:19.929974+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;p&gt;时光啊,犹如白驹过隙一转眼的时间,一个年头又这么匆匆的离去了,今天是2022年的最后一天,回望过去这一年的时光,当时立下的Flag只是实现了一少部分,简单的总结一下2022这个年头。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;已完成的目标&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;学习了Vue框架,并使用Vue框架重构了论坛开源项目的后台管理页面(部分功能重构完成了),并应用到实际的工作中去(重构了公司内部的一个平台,但是未上线,公司新的平台使用的前后端分离架构,前端使用的Vue)&lt;/li&gt;&#13;
	&lt;li&gt;一个已完成的目标,但是不想公之于众(嘻嘻!)&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;未完成的目标&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;&lt;s&gt;每个月最少更新一篇博客&lt;/s&gt;(在归档中查看只有1、2、4、5、9月更新了)&lt;/li&gt;&#13;
	&lt;li&gt;&lt;s&gt;考驾照&lt;/s&gt;(目前还只通过最简单的科目一)&lt;/li&gt;&#13;
	&lt;li&gt;&lt;s&gt;瘦下来20斤肉&lt;/s&gt;(不减反增了,哎!)&lt;/li&gt;&#13;
	&lt;li&gt;&lt;s&gt;深入MySQL的学习&lt;/s&gt;(目前还只看了前半部分的内容,拖延症晚期)&lt;/li&gt;&#13;
	&lt;li&gt;&lt;s&gt;完成论坛项目的系列教程更新&lt;/s&gt;(目前只更新到了第10节,今年就更新了一节内容)&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&#13;
&lt;p&gt;上述未完成的目标又只能顺利延期到2023年了,哈哈哈,拖延症晚期真的没得治啊!!!&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/58/" rel="alternate"/>
  </entry>
  <entry>
    <id>57</id>
    <title>[Python]Flask-SQLAlchemy一些高级用法记录</title>
    <updated>2023-09-27T02:08:19.929947+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="1. 使用Mixin" name="1. 使用Mixin"&gt;&lt;/a&gt;1. 使用Mixin定义共有字段&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;使用ORM时,我们首先必须得先通过Class定义一个数据库表的模型对象,例如我有一张emp表,如下所示&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/4010image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;如果我们想通过ORM去操作这张表的话,首先需要通过定义Class来映射&lt;span class="marker"&gt;emp表&lt;/span&gt;的结构,如下面的代码所示,这样我们需要操作emp表的话,就可以直接通过Employee这个类来进行操作了。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class Employee(db.Model):&#13;
    __tablename__ = 'emp'&#13;
&#13;
    id = db.Column(db.INTEGER, primary_key=True, autoincrement=True)&#13;
    username = db.Column(db.String(128), default='')&#13;
    password = db.Column(db.String(128), default='')&#13;
    department = db.Column(db.INTEGER, default=0, comment='')&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在很多场景,都需要将表中记录的创建时间以及修改时间记录下来,比如上述的&lt;span class="marker"&gt;emp&lt;/span&gt;表,如果需要记录,则需要在&lt;span class="marker"&gt;Employee &lt;/span&gt;的定义中新增两个字段,如下所示&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class Employee(db.Model):&#13;
    ...&#13;
    create_time = db.Column(db.DATETIME, default=datetime.now)&#13;
    update_time = db.Column(db.DATETIME, default=datetime.now, onupdate=datetime.now)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;如果在其他表中也需要记录这两个字段呢,这时候我们就可以通过Mixin来将每个都有的公共字段定义在一个Class里面,然后通过继承该&lt;span class="marker"&gt;Mixin&lt;/span&gt;使得每个表结构中都包含该公有字段&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class TimestampMixin(object):&#13;
    create_time = db.Column(db.DateTime, default=db.func.now())&#13;
    update_time = db.Column(db.DateTime, default=db.func.now(), onupdate=db.func.now())&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;然后将Employee类继承自TimestampMixin类既可&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class Employee(TimestampMixin, db.Model):&#13;
    __tablename__ = 'emp'&#13;
&#13;
    id = db.Column(db.INTEGER, primary_key=True, autoincrement=True)&#13;
    username = db.Column(db.String(128), default='')&#13;
    password = db.Column(db.String(128), default='')&#13;
    department = db.Column(db.INTEGER, default=0, comment='')&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;这是查看emp的表结构,如下图所示&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/8056image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;可以发现,继承的两个字段被自动定义在了最前面了,这与平时的习惯不一致,一般都是ID为第一个字段,这种时间戳的字段一般都是放在表的末尾,那么要怎么做,才能将公共字段自动定义到表的末尾呢?可以通过SQLAlchemy里面的declared_attr装饰器去实现,如下代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class TimestampMixin(object):&#13;
&#13;
    @declared_attr&#13;
    def created_time(cls):&#13;
        return db.Column(db.DateTime, default=datetime.now)&#13;
&#13;
    @declared_attr&#13;
    def updated_time(cls):&#13;
        return db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;此时再去查看emp表的结构,就会发现公共字段被自动的定义到了末尾了,如下图&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/6977image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="2. 使用JOIN" name="2. 使用JOIN"&gt;&lt;/a&gt;2. 使用JOIN&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;JOIN在MySQL语法中使用的非常之多,常见的有JOIN、RIGHT JOIN、LEFT JOIN三种形式,通过SQLAlchemy也可以实现这三种形式,跟随这第一节的例子,继续创建一张department表,用来存储部门相关的信息,如下代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class Department(TimestampMixin, db.Model):&#13;
    __tablename__ = 'dept'&#13;
&#13;
    id = db.Column(db.INTEGER, primary_key=True, autoincrement=True)&#13;
    name = db.Column(db.String(128), default='')&#13;
&#13;
    def __init__(self, name):&#13;
        self.name = name&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;emp表通过字段department与dept表进行关联,这里是通过逻辑外键进行关联的,而不是通过物理外键进行关联的,因此如果需要进行链表查询的话,就需要用JOIN的方式,比如查询部门为研发部的员工,可以通过下面的代码进行查询&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;emp = Employee.query.join(Department, Employee.department == Department.id).filter(Department.name == '研发部').all()&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;默认的JOIN为内连接,如果需要使用左连接,可以是有&lt;span class="marker"&gt;outerjoin&lt;/span&gt;或者设置&lt;span class="marker"&gt;is_outer=True&lt;/span&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;emp = Employee.query.outerjoin(Department, Employee.department == Department.id).filter(Department.name == '研发部').all()&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;emp = Employee.query.join(Department, Employee.department == Department.id, is_outer=True).filter(Department.name == '研发部').all()&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;右连接跟左连接一样,想使用右连接直接调换一下表的顺序即可!&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="3. 选择字段" name="3. 选择字段"&gt;&lt;/a&gt;3. 选择字段&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在MySQL中SELECT * 表示选择所有字段,与此同时,也可以通过指定字段来使得查询结果中只包含有指定指端的值,在SQLAlchemy中可以通过with_entities来指定查询结果中返回哪些字段,在第二节中,使用了JOIN但是没有指定字段,因此还是无法获取到员工所在的部门信息,通过with_entities就可以获取员工所在部门的信息了,代码如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;emp = Employee.query.join(Department, Employee.department == Department.id).with_entities(Employee.username, Department.name).filter(&#13;
        Department.name == '研发部').all()&#13;
print(str(emp))&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;查询结果&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/6884image.png" style="height:36px; width:415px" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;with_entities函数除了可以指定模型类的字段外,还可以在其中使用函数,比如我想查询各个部门的人数,代码如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;emp = Employee.query.join(Department, Employee.department == Department.id).with_entities(Department.name,db.func.count( Employee.id)).group_by(Department.name).all()&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;查询结果&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/6414image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;完整的是示例代码请访问&lt;a href="https://github.com/weijiang1994/blog-demo"&gt;https://github.com/weijiang1994/blog-demo&lt;/a&gt;获取!&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/57/" rel="alternate"/>
  </entry>
  <entry>
    <id>56</id>
    <title>[应用部署]使用Docker部署flask应用</title>
    <updated>2023-09-27T02:08:19.929920+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="1. 安装Docker" name="1. 安装Docker"&gt;&lt;/a&gt;1. 安装Docker&lt;/h1&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;本文所有操作均在ubuntu2204上进行,请知悉!同时Docker的官方文档最开始使用的dockerfile,但是对于新手来说,我个人感觉dockerfile不是太友好(比如对于我来说),因此本文只介绍如何手动在Docker容器里面部署flask应用!&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;通过apt命令查看软件源中是否包含有docker&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;apt-cache policy docker.io&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;如果出现类似下图的结果,则说明软件源中存在有docker的安装包直接通过下面的命令进行安装即可&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/9635image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo apt-get install docker.io&#13;
# 查看docker版本详细信息&#13;
sudo docker version&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;如果显示如下图所示的信息,则说明docker成功安装了&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/7592image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;如果软件源里面没有docker的包,则可以直接去&lt;a href="https://docs.docker.com/desktop/linux/install/"&gt;docker官网下载&lt;/a&gt;,然后通过&lt;span class="marker"&gt;dpkg -i&lt;/span&gt;命令进行安装&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="2. 创建Docker容器" name="2. 创建Docker容器"&gt;&lt;/a&gt;2. 创建Docker容器&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在docker中有两个概念:镜像、容器。镜像顾名思义,就是我们平时所说的操作系统比如&lt;span class="marker"&gt;ubuntu、centos&lt;/span&gt;等,但是在docker中的镜像一般都是经过&lt;strong&gt;精简后只提供最小的运行单元&lt;/strong&gt;,比如你通过docker pull ubuntu 会自动将最新的ubuntu镜像从docker的服务器拉去到本地,通过docker images 可以看到如下图所示的信息,镜像的大小只有77.8MB.&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/5979image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;镜像是容器的基础,一个镜像可以创建多个容器,容器就是运行web应用的宿主机,简而言之就是类似于虚拟机,但是跟虚拟机还是有很大的差别,镜像与容器的关系如下图所示。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/8842%E9%95%9C%E5%83%8F%E4%B8%8E%E5%AE%B9%E5%99%A8%20%282%29.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;通过下面的命令就可以创建我们自己的docker容器了&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo docker pull ubuntu # 从docker镜像仓库中拉去ubuntu最新的镜像&#13;
sudo docker run -p 8001:5000 -it ubuntu /bin/bash # 通过本地的ubuntu镜像去创建容器&#13;
sudo docker ps -a&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;通过上面的命令就可以创建一个docker容器了,结果如下图所示则说明容器创建成功了,下面解释一下上面的参数&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;-p 由于我们是将web应用部署在docker容器内部,容器对于宿主机来说是两个不同的环境,通过-p参数就可以将容器的端口映射到宿主机的端口,比如外部客户端访问宿主机的8000端口的服务器,实际上是访问到容器内部5000端口的服务&lt;/li&gt;&#13;
	&lt;li&gt;-it 加上这个参数我们就可以通过bash的方式进入容器内部&lt;/li&gt;&#13;
	&lt;li&gt;-a ps命令默认只会显示出正在运行的容器,加上-a参数可以显示本机所有的容器&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/600image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;创建好容器之后,就可以通过start命令去运行容器了,显示下图的信息就说明容器已经成功运行了&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo docker start adoring_wescoff # 跟容器名称&#13;
sudo docker ps &lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/1877image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="3. 进入容器手动部署应用" name="3. 进入容器手动部署应用"&gt;&lt;/a&gt;3. 进入容器手动部署应用&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;容器成功启动后,我们就可以通过exec命令进入容器内部,然后部署我们的应用了,不过在这之前我们可以通过rename去给容器重新修改一个名字,因为我们在执行run命令的时候,没有通过name参数给容器指定一个名称,因此docker会自动给我们的容器创建一个随机的名称,如下图所示,将容器重新命名为flask_app,然后通过ps命令去查看,容器显示命名称为flask_app,则说明容器名称修改成功。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo docker rename adoring_wescoff flask_app&#13;
sudo docker ps &lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/8720image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;成功修改容器名称后,通过exec命令进入容器内部&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo docker exec -it flask_app /bin/bash&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/6146image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在根目录中通过下面的命令创建一个简单的flask应用,并启动&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;mkdir demo &#13;
apt update &#13;
apt install python3 python3-pip vim # 安装一些必要的包&#13;
pip install flask &#13;
cd demo&#13;
vim app.py&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在app.py文件中输入下面的代码,通过python app.py运行应用&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask import Flask&#13;
&#13;
app = Flask(__name__)&#13;
&#13;
@app.route('/')&#13;
def index():&#13;
    return "Hello, World![FROM DOCKER]"&#13;
&#13;
&#13;
if __name__ == '__main__':&#13;
    app.run(host="0.0.0.0")&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在宿主机中打开浏览器,访问http://127.0.0.1:8000 即可看到下面的内容&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/2778image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;到此为止,使用docker容器手动部署flask web应用就完成了。&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;手动部署肯定不是最好的方式,但是我感觉适合我这种对Linux比较熟悉,但是对docker不是太熟悉的人,后面会继续更新通过DockerFile进行部署的内容!&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/56/" rel="alternate"/>
  </entry>
  <entry>
    <id>55</id>
    <title>[系列教程]使用Flask搭建一个校园论坛10-个人中心(1)</title>
    <updated>2023-09-27T02:08:19.929890+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="1. 功能介绍" name="1. 功能介绍"&gt;&lt;/a&gt;1. 功能介绍&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;论坛这种内容管理系统必不可少的一个功能模块就是个人中心,在个人中心中可以查看自己的账号基本信息、以及个人在社区的一些操作记录,比如发帖、评论、收藏等等,本论坛的个人中心功能如下图所示&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="个人中心功能脑图" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/3526image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="2. 页面布局" name="2. 页面布局"&gt;&lt;/a&gt;2. 页面布局&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;个人中心的页面布局如下图所示,上面个人基本信息栏为个人中心所有页面通用部分,不管在个人中心的哪个栏目,上面通用部分的信息内容是不会改变的,下方的&lt;span class="marker"&gt;四个tab&lt;/span&gt;分别对应不同的功能模块,是差异性内容。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="布局" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/9269image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;2.1 通用部分&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;在&lt;span class="marker"&gt;Jinja2&lt;/span&gt;的模板语法中,我们可以通过&lt;span class="marker"&gt;include&lt;/span&gt;关键字来将其他的模板文件引入到我们目标模板文件中,因为个人中心的上半部分的信息是通用,因此我们可以将其抽离出来,放入单独的html模板文件中去。除此之外&lt;span class="marker"&gt;Jinja2&lt;/span&gt;还支持&lt;span class="marker"&gt;macro&lt;/span&gt;块,&lt;span class="marker"&gt;macro&lt;/span&gt;的语法如下所示&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;# macro.html&#13;
{% macro sample() %}&#13;
   &amp;lt;p&amp;gt;I am macro function!&amp;lt;/p&amp;gt;&#13;
{% endmacro %}&#13;
&#13;
# other.html&#13;
&#13;
{% from  macro.html import sample with context %}&#13;
&amp;lt;div&amp;gt;&#13;
    {{ sample() }}&#13;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;为了方便管理,我们把所有的&lt;span class="marker"&gt;macro&lt;/span&gt;相关的都放到了&lt;span class="marker"&gt;templates/macro.html&lt;/span&gt;文件中,在文件中嵌入下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;{% macro profile_header() %}&#13;
    &amp;lt;div class="row"&amp;gt;&#13;
        &amp;lt;div class="col-md-3 col-lg-3 text-center"&amp;gt;&#13;
            &amp;lt;img class="img-profile-bg" alt="{{ user.nickname }}" src="{{ user.avatar }}"&amp;gt;&#13;
        &amp;lt;/div&amp;gt;&#13;
        &amp;lt;div class="col-md-9 col-lg-9 div-center-md"&amp;gt;&#13;
            &amp;lt;h3 class="mb-0"&amp;gt;{{ user.nickname }}&amp;lt;/h3&amp;gt;&#13;
            &amp;lt;p class="text-muted mb-2"&amp;gt;@{{ user.username }}&amp;lt;/p&amp;gt;&#13;
            {% if user.slogan %}&#13;
                &amp;lt;p class="mb-2"&amp;gt;{{ user.slogan }}&amp;lt;/p&amp;gt;&#13;
            {% endif %}&#13;
            {% if user.website or user.location %}&#13;
                &amp;lt;p class="mb-2"&amp;gt;&#13;
                    {% if user.website %}&#13;
                        &amp;lt;a class="text-decoration-none social-label" href="{{ user.website }}" target="_blank"&#13;
                           title="{{ user.nickname }}的个人主页"&amp;gt;&amp;lt;i class="fa fa-home fa-fw mr-1"&amp;gt;&amp;lt;/i&amp;gt;{{ user.website }}&amp;lt;/a&amp;gt;&#13;
                    {% endif %}&#13;
                    {% if user.location %}&#13;
                        &amp;lt;span class="social-label d-lg-inline d-none"&amp;gt;&amp;lt;i&#13;
                                class="fa fa-map-marker fa-fw mr-1"&amp;gt;&amp;lt;/i&amp;gt;{{ user.location }}&#13;
                        &amp;lt;/span&amp;gt;&#13;
                        &amp;lt;span class="social-label d-lg-none d-md-block" title="{{ user.location }}"&amp;gt;&amp;lt;i&#13;
                                class="fa fa-map-marker fa-fw mr-1"&amp;gt;&amp;lt;/i&amp;gt;{{ user.location|truncate(8) }}&#13;
                        &amp;lt;/span&amp;gt;&#13;
                    {% endif %}&#13;
                &amp;lt;/p&amp;gt;&#13;
            {% endif %}&#13;
            &amp;lt;p&amp;gt;&#13;
                &amp;lt;span class="mr-2"&amp;gt;{{ user.post|length }}篇帖子&amp;lt;/span&amp;gt;&#13;
                &amp;lt;a href="{{ url_for('profile.follower', user_id=user.id) }}"&#13;
                   class="mr-2 span-hand a-link text-decoration-none"&amp;gt;{{ user.followers.count() }}位粉丝&amp;lt;/a&amp;gt;&#13;
                &amp;lt;a href="{{ url_for('profile.following', user_id=user.id) }}"&#13;
                   class="mr-2 span-hand a-link text-decoration-none"&amp;gt;{{ user.following.count() }}个关注&amp;lt;/a&amp;gt;&#13;
            &amp;lt;/p&amp;gt;&#13;
            {% if current_user.id == user.id %}&#13;
                &amp;lt;a class="btn btn-outline-success" href="{{ url_for('user.index', user_id=current_user.id) }}"&amp;gt;个人主页&amp;lt;/a&amp;gt;&#13;
            {% else %}&#13;
                {% if current_user.is_following(user) %}&#13;
                    &amp;lt;a class="btn btn-outline-info" href="/profile/unfollow/{{ user.id }}/"&amp;gt;取消关注&amp;lt;/a&amp;gt;&#13;
                {% else %}&#13;
                    &amp;lt;a class="btn btn-outline-info" href="/profile/follow/{{ user.id }}/"&amp;gt;关注TA&amp;lt;/a&amp;gt;&#13;
                {% endif %}&#13;
                &amp;lt;!-- 弹出私信窗口 --&amp;gt;&#13;
                &amp;lt;a class="btn btn-outline-primary" data-toggle="modal" data-userid="{{ user.id }}"&#13;
                   data-target="#privacyChat"&amp;gt;私信&amp;lt;/a&amp;gt;&#13;
                {% if not blocked %}&#13;
                    &amp;lt;a class="btn btn-outline-danger" data-toggle="modal" data-userid="{{ user.id }}"&#13;
                       data-target="#confirmBlockUser"&amp;gt;Block&amp;lt;/a&amp;gt;&#13;
                {% else %}&#13;
                    &amp;lt;a class="btn btn-primary disabled" data-toggle="modal" data-userid="{{ user.id }}"&#13;
                       data-target="#confirmBlockUser"&amp;gt;Blocked&amp;lt;/a&amp;gt;&#13;
                {% endif %}&#13;
            {% endif %}&#13;
&#13;
        &amp;lt;/div&amp;gt;&#13;
    &amp;lt;/div&amp;gt;&#13;
{% endmacro %}&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;个人中心除了用户自己的,用户同样也可以访问其他用户的个人中心主页,因此在上面的代码中加入了判断,当前个人中心主页是否为当前用户个人的主页,如果不是,则在下方添加不同的功能按钮。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;2.2 TAB部分&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;上面的通用部分内容既可以通过macro实现也可以通过include去实现,tab部分的内容就通过macro实现就比较方便了,在macro中我们也可以传入参数,同样的在macro.html中增加下列代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;{% macro profile_moment(tabName) %}&#13;
    &amp;lt;div class="mt-4"&amp;gt;&#13;
        &amp;lt;ul class="nav nav-tabs nav-justified"&amp;gt;&#13;
            &amp;lt;li class="nav-item"&amp;gt;&#13;
                &amp;lt;a class="nav-link {% if tabName == '帖子' %}active{% endif %}" href="/profile/user/{{ user.id }}/"&amp;gt;帖子&amp;lt;/a&amp;gt;&#13;
            &amp;lt;/li&amp;gt;&#13;
            &amp;lt;li class="nav-item"&amp;gt;&#13;
                &amp;lt;a class="nav-link {% if tabName == '评论' %}active{% endif %}"&#13;
                   href="/profile/comment/{{ user.id }}/"&amp;gt;评论&amp;lt;/a&amp;gt;&#13;
            &amp;lt;/li&amp;gt;&#13;
            &amp;lt;li class="nav-item"&amp;gt;&#13;
                &amp;lt;a class="nav-link {% if tabName == '收藏' %}active{% endif %}"&#13;
                   href="/profile/collect/{{ user.id }}/"&amp;gt;收藏&amp;lt;/a&amp;gt;&#13;
            &amp;lt;/li&amp;gt;&#13;
            &amp;lt;li class="nav-item"&amp;gt;&#13;
                &amp;lt;a class="nav-link {% if tabName == '社交' %}active{% endif %}"&#13;
                   href="/profile/social/{{ user.id }}/"&amp;gt;社交&amp;lt;/a&amp;gt;&#13;
            &amp;lt;/li&amp;gt;&#13;
        &amp;lt;/ul&amp;gt;&#13;
    &amp;lt;/div&amp;gt;&#13;
{% endmacro %}&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;这里与上面稍微有些不同,这里的&lt;span class="marker"&gt;macro函数带有参数输入&lt;/span&gt;,我们在函数体中通过判断参数的不同实现对应ab的被选中,当然这里也可以通过&lt;span class="marker"&gt;include&lt;/span&gt;去实现,可以通过&lt;span class="marker"&gt;endpoint&lt;/span&gt;来设置对应tab被选中。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="3. 关注、取消关注用户" name="3. 关注、取消关注用户"&gt;&lt;/a&gt;3. 关注、取消关注用户&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;很多社交网站都会给用户提供关注的功能,论坛也提供了这个功能,用户既可以被别人关注,同时用户也可以关注他们,所以是多对多的关系,&lt;span class="marker"&gt;Follow&lt;/span&gt;表结构如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class Follow(db.Model):&#13;
    __tablename__ = 't_follow'&#13;
&#13;
    id = db.Column(db.INTEGER, primary_key=True, autoincrement=True)&#13;
    follower_id = db.Column(db.INTEGER, db.ForeignKey('t_user.id'))&#13;
    followed_id = db.Column(db.INTEGER, db.ForeignKey('t_user.id'))&#13;
&#13;
    # 正在关注用户的人&#13;
    follower = db.relationship('User', foreign_keys=[follower_id], back_populates='following', lazy='joined')&#13;
    # 用户自己正在关注的人&#13;
    followed = db.relationship('User', foreign_keys=[followed_id], back_populates='followers', lazy='joined')&#13;
&#13;
&#13;
class User(db.Model, db.UserMixin):&#13;
    ...&#13;
    following = db.relationship('Follow', foreign_keys=[Follow.follower_id], back_populates='follower',&#13;
                                lazy='dynamic', cascade='all')&#13;
    followers = db.relationship('Follow', foreign_keys=[Follow.followed_id], back_populates='followed',&#13;
                                lazy='dynamic', cascade='all')&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;这里需要注意的是,由于Follow表中的两个字段&lt;span class="marker"&gt;follower_id&lt;/span&gt;, &lt;span class="marker"&gt;followed_id&lt;/span&gt;都是user表的外键,因此我们在定义的时候需要指定每个关系所对应的字段,不然ORM框架会识别不了,&lt;strong&gt;follower指定为followe_id,followed指定为followed_id,&lt;/strong&gt;与此同时,在user表中同样也是指定。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;创建好Follow表之后,关注的逻辑就比较简单了,在&lt;span class="marker"&gt;blueprint/profile.py&lt;/span&gt;文件中增加下面的代码。代码逻辑十分简单,就是通过向Follow表中新增或者删除相应的数据,实现关注或者取消关注功能,同时还增加了自己不能关注自己的逻辑。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@profile_bp.route('/follow/&amp;lt;user_id&amp;gt;/', methods=['GET', 'POST'])&#13;
@login_required&#13;
def follow_user(user_id):&#13;
    user = User.query.get_or_404(user_id)&#13;
    if user.id == current_user.id:&#13;
        flash('我关注我自己?禁止套娃!', 'info')&#13;
        return redirect(request.referrer)&#13;
    if current_user.is_following(user):&#13;
        flash('你已经关注TA了!', 'info')&#13;
        return redirect(request.referrer)&#13;
    current_user.follow(user)&#13;
    flash('关注成功!', 'success')&#13;
    return redirect(request.referrer)&#13;
&#13;
&#13;
@profile_bp.route('/unfollow/&amp;lt;user_id&amp;gt;/', methods=['GET', 'POST'])&#13;
@login_required&#13;
def unfollow_user(user_id):&#13;
    user = User.query.get_or_404(user_id)&#13;
    if current_user.is_following(user):&#13;
        current_user.unfollow(user)&#13;
    if request.method == 'POST':&#13;
        return jsonify({'tag': 1})&#13;
    flash('取关成功!', 'success')&#13;
    return redirect(request.referrer)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="4. 用户帖子" name="4. 用户帖子"&gt;&lt;/a&gt;4. 用户帖子&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;如上面所说,个人中心用户即可以访问自己的也可以访问他人,因此需要在帖子列表上做区分,在frontend/profile.py文件中增加下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@profile_bp.route('/user/&amp;lt;user_id&amp;gt;/')&#13;
@login_required&#13;
def index(user_id):&#13;
    page = request.args.get('page', default=1, type=int)&#13;
    user = User.query.get_or_404(user_id)&#13;
    per_page = current_app.config['BBS_PER_PAGE']&#13;
    # 其他人查看用户信息时候屏蔽用户匿名发表的帖子&#13;
    if current_user.id == int(user_id):&#13;
        pagination = Post.query.filter(Post.author_id == user_id, Post.status_id == 1).order_by(&#13;
            Post.create_time.desc()).paginate(page=page, per_page=per_page)&#13;
        posts = pagination.items&#13;
    else:&#13;
        pagination, posts = get_range_post(user.id,&#13;
                                           page=page,&#13;
                                           per_page=per_page,&#13;
                                           range_day=TIME_RANGE.get(user.range_post.name))&#13;
    blocked = BlockUser.query.filter(BlockUser.user_id == current_user.id,&#13;
                                     BlockUser.block_user_id == user_id).all()&#13;
    return render_template('frontend/profile/profile.html', user=user, tag=pagination.total &amp;gt; per_page,&#13;
                           pagination=pagination, posts=posts, blocked=blocked)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;代码的逻辑很简单,如下&lt;/p&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;通过路由参数中的user_id来获取对应的user,如果没有找到user则返回404;&lt;/li&gt;&#13;
	&lt;li&gt;判断当前user是否为路由参数中的user,如果不是,则只获取该用户状态为正常的帖子;&lt;/li&gt;&#13;
	&lt;li&gt;如果是则获取所有的状态的帖子;&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;上面还出现了BlockUser的相关代码,这个代码在后续用户主页的相关文章中进行讲解,这里暂时不做讲解!&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;后端后去数据后,需要通过Jinja2模板进行渲染了,在&lt;span class="marker"&gt;frontend/profile/profile.html&lt;/span&gt;中新增下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;{% extends "frontend/base.html" %}&#13;
{% from "macro.html" import profile_header, profile_moment, render_pagination, post_list with context %}&#13;
{% block head %}&#13;
    {{ super() }}&#13;
    &amp;lt;style&amp;gt;&#13;
&#13;
        .a-title {&#13;
            font-size: 22px;&#13;
            font-weight: bold;&#13;
        }&#13;
&#13;
        .a-title-sm {&#13;
            font-size: 16px;&#13;
            font-weight: bold;&#13;
        }&#13;
&#13;
        .f-12-b {&#13;
            font-weight: bold;&#13;
            font-size: 12px;&#13;
        }&#13;
&#13;
        .span-hand {&#13;
            cursor: pointer;&#13;
        }&#13;
&#13;
    &amp;lt;/style&amp;gt;&#13;
{% endblock %}&#13;
&#13;
{% block title %}&#13;
    {{ user.nickname }}的个人主页&#13;
{% endblock %}&#13;
{% block content %}&#13;
    {{ moment.locale(auto_detect=True) }}&#13;
    &amp;lt;body&amp;gt;&#13;
    &amp;lt;main&amp;gt;&#13;
        &amp;lt;div class="container mt-2"&amp;gt;&#13;
            {% include "_flash.html" %}&#13;
            {{ profile_header() }}&#13;
            {{ profile_moment('帖子') }}&#13;
            {% if posts %}&#13;
                &amp;lt;div class="mt-2"&amp;gt;&#13;
                    {{ post_list(posts) }}&#13;
                    {% if user.range_post.name != '全部' and current_user.id != user.id %}&#13;
                        &amp;lt;div class="mt-2 text-center"&amp;gt;&#13;
                            &amp;lt;p class="text-muted"&amp;gt;&amp;lt;b&amp;gt;该用户仅展示最近{{ user.range_post.name }}的帖子&amp;lt;/b&amp;gt;&amp;lt;/p&amp;gt;&#13;
                        &amp;lt;/div&amp;gt;&#13;
                    {% endif %}&#13;
                &amp;lt;/div&amp;gt;&#13;
            {% else %}&#13;
                {% if current_user.id == user.id %}&#13;
                    &amp;lt;div class="text-center mt-5"&amp;gt;&#13;
                        &amp;lt;p class="text-muted"&amp;gt;你还没有发送过帖子!&amp;lt;/p&amp;gt;&#13;
                    &amp;lt;/div&amp;gt;&#13;
                {% else %}&#13;
                    &amp;lt;div class="text-center mt-5"&amp;gt;&#13;
                        {% if user.available_post_counts()|length %}&#13;
                            {% if user.range_post.name == '隐藏' %}&#13;
                                &amp;lt;p class="text-muted"&amp;gt;&amp;lt;b&amp;gt;该用户{{ user.range_post.name }}了帖子&amp;lt;/b&amp;gt;&amp;lt;/p&amp;gt;&#13;
                            {% else %}&#13;
                                &amp;lt;p class="text-muted"&amp;gt;&amp;lt;b&amp;gt;该用户仅展示最近{{ user.range_post.name }}的帖子&amp;lt;/b&amp;gt;&amp;lt;/p&amp;gt;&#13;
                            {% endif %}&#13;
                        {% else %}&#13;
                            &amp;lt;p class="text-muted"&amp;gt;&amp;lt;b&amp;gt;他还没有发送过帖子!&amp;lt;/b&amp;gt;&amp;lt;/p&amp;gt;&#13;
                        {% endif %}&#13;
                    &amp;lt;/div&amp;gt;&#13;
                {% endif %}&#13;
            {% endif %}&#13;
            {% include "follow-modal.html" %}&#13;
        &amp;lt;/div&amp;gt;&#13;
    &amp;lt;/main&amp;gt;&#13;
    &amp;lt;/body&amp;gt;&#13;
&#13;
    {% include "frontend/profile/contact-modal.html" %}&#13;
    {% include "frontend/profile/block-user-modal.html" %}&#13;
{% endblock %}&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;博客文章中的代码只截取了部分关键代码,很多细节代码没有贴出来,如果想要在本地机器上运行调试可以访问我的&lt;a href="https://github.com/weijiang1994/university-bbs"&gt;github仓库&lt;/a&gt;或者&lt;a href="https://gitee.com/weiijang/university-bbs"&gt;gitee仓库&lt;/a&gt;仓库,如果想要看到实际效果请访问&lt;a href="http://bbs.2dogz.cn/"&gt;二狗学院&lt;/a&gt;!&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/55/" rel="alternate"/>
  </entry>
  <entry>
    <id>54</id>
    <title>[Python]flask-babel在工厂模式下的使用</title>
    <updated>2023-09-27T02:08:19.929863+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="1. 使用flask-babel国际化流程" name="1. 使用flask-babel国际化流程"&gt;&lt;/a&gt;1. 使用flask-babel国际化流程&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;通过flask-babel的官方文档以及网络上的一些简单示例教程,flask-babel进行国际化的流程如下图所示&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/6201image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;首先通过&lt;span class="marker"&gt;pip install flask-babel&lt;/span&gt;进行扩展的安装&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;pip install flask-babel&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;安装完成之后通过下面的代码进行初始化&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask import Flask&#13;
from flask_babel import Babel&#13;
&#13;
app = Flask(__name__)&#13;
babel = Babel(app)&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;初始化完成之后就需要在项目的根目录新建&lt;span class="marker"&gt;flask-babel&lt;/span&gt;的配置文件&lt;span class="marker"&gt;babel.cfg&lt;/span&gt;,文件名取什么都无所谓,这里只是好做区分,在&lt;span class="marker"&gt;babel.cfg&lt;/span&gt;文件中一般会输入下面的内容即可&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-ini"&gt;[python: **.py]&#13;
[jinja2: **/templates/**.html]&#13;
extensions=jinja2.ext.autoescape,jinja2.ext.with_&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;之后通过&lt;span class="marker"&gt;extract&lt;/span&gt;命令将需要翻译的字符串抽离出来保存到根目录的模板文件中去&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;pybabel extract -F babel.cfg -o messages.pot .&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;这里的&lt;span class="marker"&gt;-F&lt;/span&gt;参数可以指定相应&lt;span class="marker"&gt;babel配置文件&lt;/span&gt;,&lt;span class="marker"&gt;-o&lt;/span&gt; 参数指定抽离的&lt;span class="marker"&gt;模板文件名称&lt;/span&gt; &lt;span class="marker"&gt;. &lt;/span&gt;则表示将模板文件保存到当前目录下,当然也可以指定到其他路径,抽离完模板文件之后,通过init命令初始化我们的目标语言模板文件,基于上一步骤抽离的&lt;span class="marker"&gt;pot模板文件&lt;/span&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;pybabel init -i messages.pot -d translations -l zh&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;span class="marker"&gt;-i&lt;/span&gt; 参数指定&lt;span class="marker"&gt;根模板文件&lt;/span&gt;的路径,&lt;span class="marker"&gt;-d&lt;/span&gt; 指定目标语言的模板文件&lt;span class="marker"&gt;保存路径&lt;/span&gt;(上面命令就是将目标语言的模板文件保存到当前目录的translations/zh目录下) &lt;span class="marker"&gt;-l&lt;/span&gt; 参数则是指定&lt;span class="marker"&gt;转换的语言&lt;/span&gt;,这里指定为中文,通过flask-babel实现国际化的流程大致就是如此,具体细节可以参考&lt;a href="https://flask-babel.tkte.ch/"&gt;官方文档&lt;/a&gt;以及网络资料。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="2. 工厂模式应用使用flask-babel" name="2. 工厂模式应用使用flask-babel"&gt;&lt;/a&gt;2. 工厂模式应用使用flask-babel&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;单文件应用都是在实例化Flask后直接通过Babel(app)进行初始化的,下面通过一个示例演示如何在工厂模式应用中使用flask-babel。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;2.1 项目目录结构&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/8232image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;项目目录结构如上图所示&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;&lt;strong&gt;app&lt;/strong&gt;: 存放flask应用源代码&lt;/li&gt;&#13;
	&lt;li&gt;&lt;strong&gt;translations&lt;/strong&gt;: 存放翻译文件&lt;/li&gt;&#13;
	&lt;li&gt;&lt;strong&gt;babel.cfg&lt;/strong&gt;: flask-babel配置文件&lt;/li&gt;&#13;
	&lt;li&gt;&lt;strong&gt;.flaskenv&lt;/strong&gt;: flask环境变量文件&lt;/li&gt;&#13;
	&lt;li&gt;&lt;strong&gt;message.pot&lt;/strong&gt;: flask-babel 模板翻译文件3&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;h2&gt;2.2 工厂模式初始化babel&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;在app目录下的__init__.py文件中输入如下代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask import Flask, request&#13;
from app.extensions import babel&#13;
from app.views.index import idx_bp&#13;
import os&#13;
&#13;
basedir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))&#13;
&#13;
&#13;
def create_app():&#13;
    app = Flask(__name__)&#13;
    app.config['BABEL_DEFAULT_LOCALE'] = 'zh'&#13;
    app.config['BABEL_TRANSLATION_DIRECTORIES'] = basedir + '/translations'&#13;
    app.register_blueprint(idx_bp)&#13;
    register_extensions(app)&#13;
&#13;
    @babel.localeselector&#13;
    def get_local():&#13;
        cookie = request.cookies.get('locale')&#13;
        if cookie in ['zh', 'en']:&#13;
            return cookie&#13;
        return request.accept_languages.best_match(app.config.get('BABEL_DEFAULT_LOCALE'))&#13;
&#13;
    return app&#13;
&#13;
&#13;
def register_extensions(app):&#13;
    babel.init_app(app)&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;flask-babel是通过装饰器函数&lt;span class="marker"&gt;localselector&lt;/span&gt;去获取当前需要渲染的语言,在&lt;span class="marker"&gt;create_app&lt;/span&gt;工厂方法中我们通过&lt;span class="marker"&gt;get_local&lt;/span&gt;函数去获取请求中cookie是否包含有local的值,如果有则返回cookie的值,如果没有则获取请求中&lt;span class="marker"&gt;accept_language&lt;/span&gt;获取与默认配置最匹配的语言,每次请求进入都会自动调用该函数,因此就可以达到动态切换语言的目的了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;与此同时,通过&lt;span class="marker"&gt;BABEL_DEFAULT_LOCALE&lt;/span&gt;以及&lt;span class="marker"&gt;BABEL_TRANSLATION_DIRECTORIES&lt;/span&gt;分别来&lt;span class="marker"&gt;配置默认语言&lt;/span&gt;以及&lt;span class="marker"&gt;默认翻译文件存储路径&lt;/span&gt;,如果不配置翻译文件默认存储位置,flask-babel默认会去app/translations目录下查找,这与我们翻译文件的路径是不一样。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;2.3 蓝图中的翻译&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;flask-babel提供了三个函数来标志我们需要翻译的字符串分别是gettext、ngettext、lazy_gettext,其中gettext可以标志单个字符串,ngettext可以标记多个字符串,lazy_gettext只在使用到该字符串的时候才进行翻译,在views文件中新建index.py文件,输入下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask import Blueprint, render_template&#13;
from flask_babel import gettext&#13;
idx_bp = Blueprint('index', __name__)&#13;
&#13;
&#13;
@idx_bp.route('/')&#13;
@idx_bp.route('/index')&#13;
def index():&#13;
    content = gettext('This is a flask-babel sample application')&#13;
    return render_template('index.html', content=content)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h2&gt;2.4 模板中的翻译&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;除了可以标记python文件中的字符串,还可以标记模板文件中的字符串,在templates目录中新建index.html文件,输入下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;&amp;lt;!DOCTYPE html&amp;gt;&#13;
&amp;lt;html lang="en"&amp;gt;&#13;
&amp;lt;head&amp;gt;&#13;
    &amp;lt;meta charset="UTF-8"&amp;gt;&#13;
    &amp;lt;title&amp;gt;Flask Babel Sample &amp;lt;/title&amp;gt;&#13;
    &amp;lt;link rel="stylesheet" href="{{ url_for('static', filename='index.css') }}"&amp;gt;&#13;
&amp;lt;/head&amp;gt;&#13;
&amp;lt;body&amp;gt;&#13;
&amp;lt;div class="container"&amp;gt;&#13;
    &amp;lt;h3&amp;gt;{{ _("Factory mode use Flask-Babel") }}&amp;lt;/h3&amp;gt;&#13;
    &amp;lt;hr&amp;gt;&#13;
    &amp;lt;p&amp;gt;1.{{ _('Blueprint Sample') }}&amp;lt;/p&amp;gt;&#13;
    &amp;lt;p&amp;gt;{{ content }}&amp;lt;/p&amp;gt;&#13;
    &amp;lt;p&amp;gt;2.{{ _('Template Sample') }}&amp;lt;/p&amp;gt;&#13;
    &amp;lt;p&amp;gt;{{ _("I have a dream that one day this nation will rise up and live out the true meaning of its creed: 'We hold these truths to be self-evident, that all men are created equal.") }}&amp;lt;/p&amp;gt;&#13;
    &amp;lt;p&amp;gt;{{ _("I have a dream that one day on the red hills of Georgia, the sons of former slaves and the sons of former slave owners will be able to sit down together at the table of brotherhood.") }}&amp;lt;/p&amp;gt;&#13;
    &amp;lt;p&amp;gt;{{ _("I have a dream that one day even the state of Mississippi, a state sweltering with the heat of injustice, sweltering with the heat of oppression, will be transformed into an oasis of freedom and justice.") }}&amp;lt;/p&amp;gt;&#13;
    &amp;lt;p&amp;gt;{{ _("I have a dream that my four little children will one day live in a nation where they will not be judged by the color of their skin but by the content of their character.") }}&amp;lt;/p&amp;gt;&#13;
    &amp;lt;button class="btn zh"&amp;gt;&amp;lt;a class="link" href=""&amp;gt;中文&amp;lt;/a&amp;gt;&amp;lt;/button&amp;gt;&#13;
    &amp;lt;button class="btn en"&amp;gt;&amp;lt;a class="link" href=""&amp;gt;English&amp;lt;/a&amp;gt;&amp;lt;/button&amp;gt;&#13;
&amp;lt;/div&amp;gt;&#13;
&amp;lt;/body&amp;gt;&#13;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在模板文件,将需要标志的字符串通过_()进行包括,为什么采用_()进行包裹,因为在babel.cfg文件中添加了extensions=jinja2.ext.autoescape,jinja2.ext.with_,在使用extract命令的时候会自动将其抽离出来。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;2.5 抽离模板生成翻译文件&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;在使用gettext或者_()标记好需要翻译的字符串之后,通过extract命令自动抽离出python文件、模板文件中所有被标记的字符串&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;pybabel extract -F babel.cfg -o message.pot .&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;运行结束后会在项目根目录中生成一个message.pot文件,文件内容如下图所示(只截取了部分内容),可以看到文件中的内容都是事先标记好的字符串&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/5718image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;上面的message.pot文件为模板文件,通过init命令可以根据模板文件初始化目标语言po文件&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;pybabel init -i message.pot -d translations -l zh&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上述命令会根据上一个步骤中生成的模板文件生成对应语言的翻译文件,在translations/zh/LC_MESSAGES目录中可以看到message.po文件,文件内容与message.pot文件内容一直,我们&lt;strong&gt;只需要在msgstr字段中填入对应语言的翻译内容即可&lt;/strong&gt;,如下代码所示&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-php"&gt;# Chinese translations for PROJECT.&#13;
# Copyright (C) 2022 ORGANIZATION&#13;
# This file is distributed under the same license as the PROJECT project.&#13;
# FIRST AUTHOR &amp;lt;EMAIL@ADDRESS&amp;gt;, 2022.&#13;
#&#13;
msgid ""&#13;
msgstr ""&#13;
"Project-Id-Version: PROJECT VERSION\n"&#13;
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"&#13;
"POT-Creation-Date: 2022-02-27 20:36+0800\n"&#13;
"PO-Revision-Date: 2022-02-27 19:54+0800\n"&#13;
"Last-Translator: FULL NAME &amp;lt;EMAIL@ADDRESS&amp;gt;\n"&#13;
"Language: zh\n"&#13;
"Language-Team: zh &amp;lt;LL@li.org&amp;gt;\n"&#13;
"Plural-Forms: nplurals=1; plural=0\n"&#13;
"MIME-Version: 1.0\n"&#13;
"Content-Type: text/plain; charset=utf-8\n"&#13;
"Content-Transfer-Encoding: 8bit\n"&#13;
"Generated-By: Babel 2.9.1\n"&#13;
&#13;
#: app/templates/index.html:10&#13;
msgid "Factory mode use Flask-Babel"&#13;
msgstr "Flask-Babel在工厂模式下的应用"&#13;
&#13;
#: app/templates/index.html:12&#13;
msgid "Blueprint Sample"&#13;
msgstr "蓝图示例"&#13;
&#13;
#: app/templates/index.html:14&#13;
msgid "Template Sample"&#13;
msgstr "模板示例"&#13;
&#13;
#: app/templates/index.html:15&#13;
msgid ""&#13;
"I have a dream that one day this nation will rise up and live out the "&#13;
"true meaning of its creed: 'We hold these truths to be self-evident, that"&#13;
" all men are created equal."&#13;
msgstr "我梦想有一天,这个国家会站立起来,真正实现其信条的真谛:“我们认为这些真理是不言而喻的——人人生而平等。”"&#13;
&#13;
#: app/templates/index.html:16&#13;
msgid ""&#13;
"I have a dream that one day on the red hills of Georgia, the sons of "&#13;
"former slaves and the sons of former slave owners will be able to sit "&#13;
"down together at the table of brotherhood."&#13;
msgstr "我梦想有一天,在佐治亚的红山上,昔日奴隶的儿子将能够和昔日奴隶主的儿子坐在一起,共叙兄弟情谊。"&#13;
&#13;
#: app/templates/index.html:17&#13;
msgid ""&#13;
"I have a dream that one day even the state of Mississippi, a state "&#13;
"sweltering with the heat of injustice, sweltering with the heat of "&#13;
"oppression, will be transformed into an oasis of freedom and justice."&#13;
msgstr "我梦想有一天,甚至连密西西比州这个正义匿迹,压迫成风的地方,也将变成自由和正义的绿洲。"&#13;
&#13;
#: app/templates/index.html:18&#13;
msgid ""&#13;
"I have a dream that my four little children will one day live in a nation"&#13;
" where they will not be judged by the color of their skin but by the "&#13;
"content of their character."&#13;
msgstr "我梦想有一天,我的四个孩子将在一个不是以他们的肤色,而是以他们的品格优劣来评价他们的国度里生活。"&#13;
&#13;
#: app/views/index.py:17&#13;
msgid "This is a flask-babel sample application"&#13;
msgstr "这是一个flask-babel的简单示例应用"&#13;
&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;这时候浏览器打开http://127.0.01:5000首页显示页面内容如下,页面显示的中文,因为默认配置的语言为zh&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/5383image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="3. 动态切换语言" name="3. 动态切换语言"&gt;&lt;/a&gt;3. 动态切换语言&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在第二节中,&lt;strong&gt;通过读取请求中cookie的值去判断当前语言&lt;/strong&gt;,因此我们可以在&lt;span class="marker"&gt;index.py&lt;/span&gt;文件中添加下面的代码实现动态语言切换功能,代码片段如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@idx_bp.route('/set-locale/&amp;lt;language&amp;gt;')&#13;
def set_locale(language):&#13;
    resp = redirect(request.referrer)&#13;
    if language:&#13;
        resp.set_cookie('locale', language, max_age=30 * 24 * 60 * 60)&#13;
    return resp&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;然后将index.html文件中两个a标签的href改为如下代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;&amp;lt;button class="btn zh"&amp;gt;&amp;lt;a class="link" href="{{ url_for('index.set_locale', language='zh') }}"&amp;gt;中文&amp;lt;/a&amp;gt;&amp;lt;/button&amp;gt;&#13;
&amp;lt;button class="btn en"&amp;gt;&amp;lt;a class="link" href="{{ url_for('index.set_locale', language='en') }}"&amp;gt;English&amp;lt;/a&amp;gt;&amp;lt;/button&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;这样就可以点击按钮就可以动态切换语言了,如下图&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/1984image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="4. 注意事项" name="4. 注意事项"&gt;&lt;/a&gt;4. 注意事项&lt;/h1&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;工厂模式将localselector装饰函数放到create_app下即可&lt;/li&gt;&#13;
	&lt;li&gt;注意翻译文件的路径,如果不是在项目源码目录下,需要配置&lt;span class="marker"&gt;BABEL_TRANSLATION_DIRECTORIES&lt;/span&gt;&lt;/li&gt;&#13;
	&lt;li&gt;如果更新了标记字符串,使用pybabel update命令不要使用init命令,否则会初始化已翻译的内容&lt;/li&gt;&#13;
	&lt;li&gt;如果更新了标记字符串,&lt;strong&gt;记得要重启应用&lt;/strong&gt;&lt;/li&gt;&#13;
	&lt;li&gt;项目代码:&lt;a href="https://github.com/weijiang1994/flask-babel-sample"&gt;https://github.com/weijiang1994/flask-babel-sample&lt;/a&gt;&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/54/" rel="alternate"/>
  </entry>
  <entry>
    <id>53</id>
    <title>[电子游戏]原神抽卡分析</title>
    <updated>2023-09-27T02:08:19.929836+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="1.声明" name="1.声明"&gt;&lt;/a&gt;1.声明&lt;/h1&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;抽卡分析记录所有者为上海米哈游影铁科技有限公司(原神发行商),如若此篇博文侵害了上海米哈游影铁科技有限公司相关利益,请联系我邮箱804022023@qq.com,我会在第一时间删除此博文,并致歉!&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;此篇博文最主要的目的是分享,请各位看官切勿拿去做一些非法交易!!!&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="2.历史记录页面分析" name="2.历史记录页面分析"&gt;&lt;/a&gt;2.历史记录页面分析&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;我们在手机上玩原神的时候,进入祈愿页面,可以在左下角看到历史记录按钮,点击历史记录按钮就可以看到近六个月的抽卡历史记录,如果接触过安卓开发的同学应该可以看出来,这个页面应该是用&lt;span class="marker"&gt;WebView&lt;/span&gt;控件去实现的,这时候我们断开网络然后点击右上角的刷新按钮,就会出现类似下图的页面,这应该是安卓&lt;span class="marker"&gt;WebView&lt;/span&gt;的机制(我的猜想,因为开发安卓已经是很多年前的事情了)。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="Android-webview无网络" class="d-block img-fluid mx-auto pic" src="http://wl.aidezy.com/2019061316320145796673.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;页面上出现的URL就是历史记录页面,也就是各种抽卡分析小程序需要你手动输入的URL,复制该URL然后粘贴到浏览器地址栏中,可以看到游戏祈愿历史记录一样的页面,如下图所示&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="祈愿历史记录页面" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/8051image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="3.历史记录API分析" name="3.历史记录API分析"&gt;&lt;/a&gt;3.历史记录API分析&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在浏览器中按F12打开开发者模式,点击到网络TAB,可以看到获取历史记录的API。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/7967image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;如上图所示,可以看到这是通过GET请求携带路径参数获取祈愿历史记录,其路径参数一览表如下所示,&lt;strong&gt;下面只说明比较重要的参数,其他的参数按照默认值即可&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;authkey: 账号认证key,就是登录认证token&lt;/li&gt;&#13;
	&lt;li&gt;page: 当前页码,不过这个不起作用&lt;/li&gt;&#13;
	&lt;li&gt;size: 每页的大小,默认为6,最大值为20&lt;/li&gt;&#13;
	&lt;li&gt;end_id: 结束ID&lt;/li&gt;&#13;
	&lt;li&gt;begin_id: 开始ID&lt;/li&gt;&#13;
	&lt;li&gt;gacha_type: 祈愿类型&#13;
	&lt;ol&gt;&#13;
		&lt;li&gt;301:角色祈愿&lt;/li&gt;&#13;
		&lt;li&gt;200:常驻祈愿&lt;/li&gt;&#13;
		&lt;li&gt;302:武器祈愿&lt;/li&gt;&#13;
	&lt;/ol&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&#13;
&lt;p&gt;复制API链接,打开postman进行访问,如下所示,可以清楚的看到可以获取到对应的JSON数据(省略了后面的数据)&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-json"&gt;{&#13;
    "retcode": 0,&#13;
    "message": "OK",&#13;
    "data": {&#13;
        "page": "1",&#13;
        "size": "6",&#13;
        "total": "0",&#13;
        "list": [&#13;
            {&#13;
                "uid": "***",&#13;
                "gacha_type": "400",&#13;
                "item_id": "",&#13;
                "count": "1",&#13;
                "time": "2022-02-08 09:39:39",&#13;
                "name": "铁影阔剑",&#13;
                "lang": "zh-cn",&#13;
                "item_type": "武器",&#13;
                "rank_type": "3",&#13;
                "id": "***"&#13;
            },&#13;
            ...&#13;
        ],&#13;
        "region": "cn_gf01"&#13;
    }&#13;
}&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;这里需要注意的authkey的必须经过&lt;span class="marker"&gt;url_encode&lt;/span&gt;编码后才是有效的&lt;span class="marker"&gt;authkey&lt;/span&gt;,否则会返回&lt;span class="marker"&gt;authkey error&lt;/span&gt;的错误!&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;然后我们修改page的值范围2,发现获取的还是第一页的数据,如下图&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/8489image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;继续在浏览器中点击下一页的按钮,我们可以发现&lt;span class="marker"&gt;end_id&lt;/span&gt;这个参数是发生变化的,如下图所示。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/7279image.png" /&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/3233image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;第一页时,&lt;span class="marker"&gt;end_id&lt;/span&gt;的值为0,从第二页开始&lt;span class="marker"&gt;end_id&lt;/span&gt;的值就发生了变化了。这里应该是通过&lt;span class="marker"&gt;JavaScript&lt;/span&gt;代码使&lt;span class="marker"&gt;end_id&lt;/span&gt;的值动态变化,后端在请求查询的时候从&lt;span class="marker"&gt;end_id&lt;/span&gt;这个值开始查询,因此如果我们end_id这个值一直不变化的话,那么永远只能获取当前这一页的值,那么end_id的值是怎么来的呢?仔细查看页面,&lt;strong&gt;我发现我浏览器右上角的vue开发工具插件亮了&lt;/strong&gt;,发现这个页面是用Vue开发的,于是我切换到开发者工具源代码页面,在bundle_***.js文件中搜索&lt;strong&gt;end_id关键字&lt;/strong&gt;,可以看到如下图所示的代码。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/2867image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;这下就豁然开朗了,&lt;span class="marker"&gt;end_id&lt;/span&gt;的值就是当前页面查询结果最后一条记录的id的值,在&lt;strong&gt;postman中手动改变end_id的值&lt;/strong&gt;,就可以愉快的获取下一页的记录啦,如下图所示&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/4348image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="4.保存历史记录到本地" name="4.保存历史记录到本地"&gt;&lt;/a&gt;4.保存历史记录到本地&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;原神祈愿历史记录页面只提供最近六个月的祈愿历史记录,因此我们可以通过代码将历史记录保存到本地,下面的Python代码就可以将历史记录保存到本地。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;import requests&#13;
import json&#13;
import datetime&#13;
import time&#13;
# 需要先安装requests库 pip install requests&#13;
&#13;
# 替换你自己的authkey,必须是url_encode编码&#13;
authkey = ''&#13;
size = 20&#13;
end_id = 0&#13;
end_flag = False&#13;
gacha_type = {301: '角色祈愿', 302: '武器祈愿', 200: '常驻祈愿'}&#13;
# 替换为自己个gacha_id&#13;
gacha_id = ''&#13;
today = datetime.datetime.today().strftime('%Y%m%d_%H%M%S')&#13;
&#13;
url = 'https://hk4e-api.mihoyo.com/event/gacha_info/api/getGachaLog?authkey_ver=1&amp;amp;sign_type=2&amp;amp;auth_appid' \&#13;
      '=webview_gacha&amp;amp;init_type=301&amp;amp;gacha_id={}&amp;amp;timestamp=1641338923&amp;amp;lang=zh-cn' \&#13;
      '&amp;amp;device_type=pc&amp;amp;game_version=CNRELWin2.4.0_R5691054_S5715829_D5736476&amp;amp;plat_type=pc&amp;amp;region=cn_gf01&amp;amp;authkey={' \&#13;
      '}&amp;amp;game_biz=hk4e_cn&amp;amp;gacha_type={}&amp;amp;page=2&amp;amp;size={}&amp;amp;end_id={}'&#13;
&#13;
for gt in gacha_type.keys():&#13;
    index = 1&#13;
    end_flag = False&#13;
    end_id = 0&#13;
    data = []&#13;
    while not end_flag:&#13;
        wishes = requests.get(url.format(gacha_id, authkey, gt, size, end_id)).json()&#13;
        print(f'正在获取{gacha_type.get(gt)}祈愿第{index}页...')&#13;
        if not wishes['data']['list']:&#13;
            end_flag = True&#13;
            print(f'{"*"*10}{gacha_type.get(gt)}祈愿获取完成!{"*"*10}')&#13;
            break&#13;
        data += wishes['data']['list']&#13;
        end_id = wishes['data']['list'][-1]['id']&#13;
        time.sleep(0.2)&#13;
        index += 1&#13;
&#13;
    with open(f'{today}_{gacha_type.get(gt)}.json', 'w', encoding='utf8') as f:&#13;
        json.dump(data, f, ensure_ascii=False, indent=2)&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="5.再次声明" name="5.再次声明"&gt;&lt;/a&gt;5.再次声明&lt;/h1&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;抽卡分析记录所有者为上海米哈游影铁科技有限公司(原神发行商),如若此篇博文侵害了上海米哈游影铁科技有限公司相关利益,请联系我邮箱804022023@qq.com,我会在第一时间删除此博文,并致歉!&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;此篇博文最主要的目的是分享,请各位看官切勿拿去做一些非法交易!!!&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/53/" rel="alternate"/>
  </entry>
  <entry>
    <id>52</id>
    <title>[Linux]Linux关于磁盘备份dd命令以及fstab文件简介</title>
    <updated>2023-09-27T02:08:19.929809+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="1. dd命令的使用" name="1. dd命令的使用"&gt;&lt;/a&gt;1. dd命令的使用&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;工作中遇到的需求就是将一台已经配置好了的环境的服务器备份系统到新的磁盘,然后将新的磁盘安装在新的服务器上可以直接启动系统,同时不需要重新配置新的运行环境了。领导说要我直接使用&lt;span class="marker"&gt;dd&lt;/span&gt;命令去做,因为之前没有用过&lt;span class="marker"&gt;dd&lt;/span&gt;命令,这里记录一下。(ps:后来也是通过dd命令完成了这项任务)&lt;/p&gt;&#13;
&#13;
&lt;p&gt;Linux &lt;span class="marker"&gt;dd&lt;/span&gt; 命令用于读取、转换并输出数据,&lt;span class="marker"&gt;dd&lt;/span&gt; 可从标准输入或文件中读取数据,根据指定的格式来转换数据,再输出到文件、设备或标准输出,其可以携带的参数(只说明一些常用参数,详细参数可参考&lt;a href="https://www.runoob.com/linux/linux-comm-dd.html"&gt;dd命令参数&lt;/a&gt;)如下所示&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;if 输入设备,输入文件名,默认为标准输入。即指定源文件&lt;/li&gt;&#13;
	&lt;li&gt;of 输出设备,输出文件名,默认为标准输出。即指定目的文件&lt;/li&gt;&#13;
	&lt;li&gt;bs 输入、输出块的大小&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;p&gt;备份一个磁盘分区到U盘&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo dd if=/dev/sda5 of=/dev/sdc&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/1987image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;默认情况下,&lt;span class="marker"&gt;bs&lt;/span&gt;的大小为&lt;span class="marker"&gt;512&lt;/span&gt;,可以通过bs参数指定其大小,指定&lt;span class="marker"&gt;bs&lt;/span&gt;大小为5&lt;span class="marker"&gt;M&lt;/span&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo dd if=/dev/sda5 of=/dev/sdc bs=5M&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/5105image.png" style="height:85px; width:581px" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;可以到拷贝的速度要比默认bs大小要快,&lt;strong&gt;至于bs的大小多少合理,应该要看磁盘的IO速度。&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;如果说备份的内容比较大,我们要等待很久,这时我们可以打开一个新的终端,然后通过&lt;span class="marker"&gt;watch&lt;/span&gt;命令去查看dd命令的执行速度,执行完下面的命令后,回到dd命令执行的终端就可以看到执行进度了,如下图,如果不想查看,在&lt;strong&gt;执行watch命令的终端&lt;/strong&gt;按&lt;kbd&gt;Ctrl+c&lt;/kbd&gt;即可关闭。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo watch -n 5 pkill -USR1 -x dd&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/8101image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h3&gt;&lt;strong&gt;dd与cp的区别&lt;/strong&gt;&lt;/h3&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;dd命令是通过数据流进行复制的,而cp命令是通过文件为单位进行拷贝的;&lt;/li&gt;&#13;
	&lt;li&gt;dd命令的of输出的大小必须大于或者等于if输入磁盘的总大小(不是使用大小),而cp命令只需要目标盘空间大于输入盘已使用空间大小即可;&lt;/li&gt;&#13;
	&lt;li&gt;dd命令执行完毕后of目标盘的分区、每个扇区的数据都与if盘一样,而cp命令只是将数据拷贝到目标盘空闲空间,扇区数据不一定与输入盘一致;&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&#13;
&lt;p&gt;如果我们需要备份系统,通过dd命令备份后,of输出盘可以直接插在其他机器上使用并顺利启动系统。&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;使用dd命令时会清空目标盘的所有数据,请确保使用前已经备份目标盘的所有数据!!!&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="2. fstab文件" name="2. fstab文件"&gt;&lt;/a&gt;2. fstab文件&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;很多服务器都是一个系统盘,然后若干个数据盘,我们通过dd命令备份系统盘之后,放在其他机器上启动时,有时候会发现进入&lt;strong&gt;rescue mode&lt;/strong&gt;,查看启动信息如果发现是&lt;strong&gt;Timed out waiting for device xxxx&lt;/strong&gt;这种错误的话&lt;strong&gt;一般是找不到fstab中指定的设备所导致的&lt;/strong&gt;,接下来就说道说道fstab这个文件的作用。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;fstab文件存在在/etc目录下,默认内容如下所示。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-objectivec"&gt;# /etc/fstab: static file system information.&#13;
#&#13;
# Use 'blkid' to print the universally unique identifier for a&#13;
# device; this may be used with UUID= as a more robust way to name devices&#13;
# that works even if disks are added and removed. See fstab(5).&#13;
#&#13;
# &amp;lt;file system&amp;gt; &amp;lt;mount point&amp;gt;   &amp;lt;type&amp;gt;  &amp;lt;options&amp;gt;       &amp;lt;dump&amp;gt;  &amp;lt;pass&amp;gt;&#13;
# /dev/sdb3 LABEL=SYSROOT&#13;
UUID=ab6a416a-8664-4704-b86d-7260625f68c9       /               ext4            rw,relatime     0 1&#13;
&#13;
# /dev/sdb2 LABEL=SYSBOOT&#13;
UUID=446bc5e4-8100-4c9a-9dbf-1a8aeb5e94fc       /boot           ext4            rw,relatime     0 0&#13;
&#13;
# /dev/sdb1 LABEL=ESP&#13;
UUID=DE13-1B25          /boot/efi       vfat            rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro    0 0&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;可以看到第一行 &lt;strong&gt;/etc/fstab: static file system information. &lt;/strong&gt;意思就是静态文件系统信息。接下来可以看到提示信息通过&lt;span class="marker"&gt;blkid&lt;/span&gt;来获取设备的通用唯一标识符UUID,fstab配置文件所提供的参数有6种,如下所示&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;&lt;strong&gt;file system:&lt;/strong&gt; 文件系统,可以通过&lt;strong&gt;/dev/xxx&lt;/strong&gt;来表示或者通过&lt;strong&gt;UUID=xxx&lt;/strong&gt;或者&lt;strong&gt;label&lt;/strong&gt;来表示,&lt;strong&gt;这里推荐使用UUID的表示来配置,因为设备ID是唯一的&lt;/strong&gt;,即使你重新插拔磁盘,其UUID是不会改变的,但是重新插拔后其/dev/xxx可能会改变,比如一开始设备编号是/dev/sdc,你插拔之后变为了/dev/sdd了,这时候如果你没改变fstab里面的内容,开机就会进入rescue mode;&lt;/li&gt;&#13;
	&lt;li&gt;&lt;strong&gt;mount point:&lt;/strong&gt; 挂载点,就是设备挂载在哪个路径下,跟我们执行mount命令时一样,这个目录必须是存在的;&lt;/li&gt;&#13;
	&lt;li&gt;&lt;strong&gt;type:&lt;/strong&gt; 文件系统类型,指定挂载设备的文件系统类型,可以通过&lt;span class="marker"&gt;blkid查看到设备对应的文件系统类型&lt;/span&gt;,如下图,常见的有ext4等&lt;/li&gt;&#13;
	&lt;li&gt;&lt;strong&gt;option:&lt;/strong&gt; 可以选参数,一般就是mount命令可以跟的参数,但是要注意有些参数只对应特定的文件系统才有效,一般填写&lt;span class="marker"&gt;defaults&lt;/span&gt;就可以了,既通过读写模式挂载,其他参数可以网上冲浪搜索;&lt;/li&gt;&#13;
	&lt;li&gt;&lt;strong&gt;dump: &lt;/strong&gt;是否使用dump工具进行备份,0表示忽略,1表示备份,填写1时你必须确保已经安装了dump工具;&lt;/li&gt;&#13;
	&lt;li&gt;pass: 是否使用fsck命令检查,可以设置为0, 1, 2,&lt;strong&gt;根文件系统必须设置为1&lt;/strong&gt;,以保证其最高优先级,2则表示检查文件系统,0表示不检查文件系统;&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-objectivec"&gt;# /dev/sda8 LABEL=WORK-DATA&#13;
UUID=39fd4aa5-e4ba-4618-8ce2-4ebe9e0325d5       /home/ubuntu/data     ext4            defaults                0 0&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;添加上面的配置信息,开机就会自动将&lt;strong&gt;/dev/sda8&lt;/strong&gt;自动挂载到&lt;strong&gt;/home/ubuntu/data&lt;/strong&gt;目录下;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/52/" rel="alternate"/>
  </entry>
  <entry>
    <id>51</id>
    <title>[MySQL]MySQL5.7版本后GROUP BY问题</title>
    <updated>2023-09-27T02:08:19.929781+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="1. 起因" name="1. 起因"&gt;&lt;/a&gt;1. 起因&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;最近在做一个开源项目,中间用到了&lt;span class="marker"&gt;group by&lt;/span&gt;查询,在本地测试的时候没有啥问题,但是项目更新到服务器上之后出现了下面的错误&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;ERROR 1055 (42000): Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column &amp;#39;bbs.t_private_message.id&amp;#39; which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;字面上的意思是&lt;span class="marker"&gt;SELECT&lt;/span&gt;里面的列必须包含在&lt;span class="marker"&gt;GROUP BY&lt;/span&gt;当中,于是网上冲浪查询相关的坑,发现有下面两种解决方案。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="2. 修改sql_mode" name="2. 修改sql_mode"&gt;&lt;/a&gt;2. 修改sql_mode&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;修改&lt;span class="marker"&gt;global.sql_mode&lt;/span&gt;有两种方式,第一种通过&lt;code&gt;SET&lt;/code&gt;语句进行修改,如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;select @@global.sql_mode&#13;
# ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION&#13;
set @@global.sql_mode = `STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION`&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;如果上面的方法没有生效,试着用下面的方法&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;select @@session.sql_mode&#13;
#ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION&#13;
set @@session.sql_mode = `STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION`&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;第二种方式是通过修改配置文件,&lt;strong&gt;这种方法的可行性有待商榷&lt;/strong&gt;,我自行修改没有生效,还会导致&lt;strong&gt;MySQL&lt;/strong&gt;服务启动不起来了&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;# 在文件最后加上下面的内容&#13;
[mysqld]&#13;
sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION&#13;
# 重启MySQL服务&#13;
sudo systemctl restart mysql.service&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="3. 使用ANY_VALUE" name="3. 使用ANY_VALUE"&gt;&lt;/a&gt;3. 使用ANY_VALUE&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;程序猿都比较喜欢折腾,我也是。总感觉MySQL自动开启&lt;span class="marker"&gt;ON_FULL_GROUP_BY&lt;/span&gt;的运行模式有它自己的考虑。因此就去MySQL官网查找相关的文档,在&lt;span class="marker"&gt;GROUP BY&lt;/span&gt;这一节开篇有下面一段话&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;SQL-92 and earlier does not permit queries for which the select list, &lt;code&gt;HAVING&lt;/code&gt; condition, or &lt;code&gt;ORDER BY&lt;/code&gt; list refer to nonaggregated columns that are not named in the &lt;code&gt;GROUP BY&lt;/code&gt; clause. For example,this query is illegal in standard SQL-92 because the nonaggregated &lt;code&gt;name&lt;/code&gt; column in the select list does not appear in the &lt;code&gt;GROUP BY&lt;/code&gt;:&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;SELECT o.custid, c.name, MAX(o.payment)&#13;
  FROM orders AS o, customers AS c&#13;
  WHERE o.custid = c.custid&#13;
  GROUP BY o.custid;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上面引用的大概意思就是在&lt;code&gt;SQL-92&lt;/code&gt;以及更早的版本&lt;span class="marker"&gt;SELECT、HAVING、ORDER BY&lt;/span&gt;中出现的字段名必须包含在&lt;span class="marker"&gt;GROUP BY&lt;/span&gt;当中,上面的查询语句当中包含有&lt;span class="marker"&gt;name&lt;/span&gt;字段,但是&lt;span class="marker"&gt;name&lt;/span&gt;字段没有包含在&lt;span class="marker"&gt;GROUP BY&lt;/span&gt;当中,所以上述SQL语句是非法的在SQL-92后的版本。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;继续往下看也看到了上面两种解决方式&lt;a href="https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html#sqlmode_only_full_group_by"&gt;sql_mode相关介绍&lt;/a&gt;,继续往下翻看到了下面的内容&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;If you know that, &lt;em&gt;for a given data set,&lt;/em&gt; each &lt;code&gt;name&lt;/code&gt; value in fact uniquely determines the &lt;code&gt;address&lt;/code&gt; value, &lt;code&gt;address&lt;/code&gt; is effectively functionally dependent on &lt;code&gt;name&lt;/code&gt;. To tell MySQL to accept the query, you can use the &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/miscellaneous-functions.html#function_any-value"&gt;&lt;code&gt;ANY_VALUE()&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;意思就是可以使用&lt;span class="marker"&gt;ANY_VALUE()&lt;/span&gt;这个函数来包裹你想要查询的列,但是这个列又可以不包裹在&lt;span class="marker"&gt;GROUP BY&lt;/span&gt;当中,如下面的SQL语句就是合法的&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;SELECT name, ANY_VALUE(address), MAX(age) FROM t GROUP BY name;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h1&gt;4. 栗子&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;说一千道一万还不如举个栗子来个实在,这个问题是在我开源项目的私信模块里面发现的,最开始我就是想通过&lt;span class="marker"&gt;GROUP BY&lt;/span&gt;查找到与当前用户相关的私信,通过&lt;span class="marker"&gt;sender_id&lt;/span&gt;来进行分组,私信表结构如下图&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/2706image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;下面就通过一个栗子来演示一下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;"""&#13;
# coding:utf-8&#13;
@Time    : 2021/11/23&#13;
@Author  : jiangwei&#13;
@File    : private_letter.py&#13;
@Desc    : private_letter&#13;
@email   : qq804022023@gmail.com&#13;
@Software: PyCharm&#13;
"""&#13;
from flask import Flask, render_template, redirect, url_for, flash&#13;
from flask_sqlalchemy import SQLAlchemy&#13;
from sqlalchemy.sql.expression import func&#13;
&#13;
from faker import Faker&#13;
import random&#13;
import datetime&#13;
&#13;
username = 'jiangwei'&#13;
password = '1994124'&#13;
database = 'demo'&#13;
f = Faker(locale='zh_CN')&#13;
app = Flask(__name__)&#13;
app.config['SQLALCHEMY_DATABASE_URI'] = f'mysql+pymysql://{username}:{password}@localhost/{database}?charset=utf8'&#13;
app.config['SECRET_KEY'] = 'sfajn1314jnm14h1'&#13;
db = SQLAlchemy(app)&#13;
&#13;
&#13;
class PrivateMessage(db.Model):&#13;
    __tablename__ = 't_private_message'&#13;
&#13;
    id = db.Column(db.INTEGER, primary_key=True, autoincrement=True)&#13;
    sender_id = db.Column(db.INTEGER, nullable=False)&#13;
    receiver_id = db.Column(db.INTEGER, nullable=False)&#13;
    content = db.Column(db.TEXT, nullable=False, default='')&#13;
    c_time = db.Column(db.DATETIME, default=datetime.datetime.now)&#13;
&#13;
&#13;
db.create_all()&#13;
&#13;
&#13;
@app.route('/')&#13;
def index():&#13;
    user_id = random.randint(1, 3)&#13;
    return render_template('index.html', user_id=user_id)&#13;
&#13;
&#13;
@app.route('/insert')&#13;
def insert():&#13;
    for i in range(10):&#13;
        pm = PrivateMessage(&#13;
            sender_id=random.randint(1, 20),&#13;
            receiver_id=random.randint(1, 3),&#13;
            content=f.sentence()&#13;
        )&#13;
        db.session.add(pm)&#13;
    db.session.commit()&#13;
    flash('插入数据成功!')&#13;
    return redirect(url_for('.index'))&#13;
&#13;
&#13;
@app.route('/insert/&amp;lt;user_id&amp;gt;')&#13;
def query(user_id):&#13;
    pms = PrivateMessage.query.\&#13;
        with_entities(PrivateMessage.sender_id,&#13;
                      func.any_value(PrivateMessage.content),&#13;
                      func.max(PrivateMessage.c_time)). \&#13;
        filter(PrivateMessage.receiver_id == user_id). \&#13;
        order_by(func.max(PrivateMessage.c_time).desc()). \&#13;
        group_by(PrivateMessage.sender_id).all()&#13;
    return render_template('query.html', pms=pms, user_id=user_id)&#13;
&#13;
&#13;
if __name__ == '__main__':&#13;
    app.run(port=5005, debug=True)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上面是一个非常简答的Flask应用,首先我们通过继承&lt;code&gt;db.Model&lt;/code&gt;来定义表&lt;code&gt;t_private_message&lt;/code&gt;的表结构,之后通过&lt;code&gt;db.create_all&lt;/code&gt;来创建表,然后实例化&lt;code&gt;Flask&lt;/code&gt;创建一个&lt;code&gt;flask&lt;/code&gt;实例,通过该实例创建了三个视图函数&lt;code&gt;index&lt;/code&gt;、&lt;code&gt;insert&lt;/code&gt;、&lt;code&gt;query&lt;/code&gt;,分别对应&lt;code&gt;首页视图&lt;/code&gt;、&lt;code&gt;插入数据视图&lt;/code&gt;、&lt;code&gt;查询数据视图&lt;/code&gt;,我们所用到的&lt;code&gt;any_value&lt;/code&gt;方法就包含在查询视图中,代码如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@app.route('/insert/&amp;lt;user_id&amp;gt;')&#13;
def query(user_id):&#13;
    pms = PrivateMessage.query.\&#13;
        with_entities(PrivateMessage.sender_id,&#13;
                      func.any_value(PrivateMessage.content),&#13;
                      func.max(PrivateMessage.c_time)). \&#13;
        filter(PrivateMessage.receiver_id == user_id). \&#13;
        order_by(func.max(PrivateMessage.c_time).desc()). \&#13;
        group_by(PrivateMessage.sender_id).all()&#13;
    return render_template('query.html', pms=pms, user_id=user_id)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在&lt;code&gt;sqlalchemy&lt;/code&gt;中我们可以使用&lt;code&gt;with_entities&lt;/code&gt;来指定我们需要查询的列,通过&lt;code&gt;any_value&lt;/code&gt;函数包裹&lt;code&gt;content、c_time&lt;/code&gt;字段,然后通过&lt;code&gt;c_time&lt;/code&gt;进行排序,最后通过&lt;code&gt;GROUP BY&lt;/code&gt;进行分组,点击首页页面上&lt;code&gt;插入数据&lt;/code&gt;按钮插入10条测试数据,然后点击&lt;code&gt;查询私信&lt;/code&gt;按钮结果如下图&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/6751image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;我们可以清楚地看到&lt;code&gt;Sender ID&lt;/code&gt;出现顺序根据&lt;code&gt;Send Time&lt;/code&gt;进行了逆序排列了,达到了最新发送的消息在最上方的目的,如下图(来自我的开源项目&lt;code&gt;university-bbs&lt;/code&gt;私信模块,请忽略聊天文本,仅仅是为了测试所用&lt;a href="!https://bbs.2dogz.cn"&gt;二狗学院&lt;/a&gt;)&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/5076image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;在&lt;code&gt;sqlalchemy&lt;/code&gt;中如果使用了&lt;code&gt;with_entities&lt;/code&gt;来指定了查询列,查询结果不再是&lt;code&gt;Object&lt;/code&gt;对象了,而是一个列表中嵌套了元祖的形式!&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;上面的代码只能在MySQL5.7以上的版本运行,因为MySQL5.7以下的版本没有&lt;code&gt;ANY_VALUE&lt;/code&gt;这个函数,因此为了适配MySQL5.7以下的版本需要改进以下代码,如下代码段所示&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@app.route('/insert/&amp;lt;user_id&amp;gt;')&#13;
def query(user_id):&#13;
    try:&#13;
        pms = PrivateMessage.query.\&#13;
            with_entities(PrivateMessage.sender_id,&#13;
                          func.any_value(PrivateMessage.content),&#13;
                          func.max(PrivateMessage.c_time)). \&#13;
            filter(PrivateMessage.receiver_id == user_id). \&#13;
            order_by(func.max(PrivateMessage.c_time).desc()). \&#13;
            group_by(PrivateMessage.sender_id).all()&#13;
    except Exception as e:&#13;
        pms = PrivateMessage.query.\&#13;
            with_entities(PrivateMessage.sender_id,&#13;
                          PrivateMessage.content,&#13;
                          func.max(PrivateMessage.c_time)).\&#13;
            filter(PrivateMessage.receiver_id == user_id).\&#13;
            order_by(func.max(PrivateMessage.c_time).desc()).\&#13;
            group_by(PrivateMessage.sender_id).all()&#13;
    return render_template('query.html', pms=pms, user_id=user_id)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;很简单就是通过try来捕获异常,如果发生异常则使用&lt;code&gt;except&lt;/code&gt;代码块里面的查询,上述示例代码存放于&lt;a href="https://github.com/weijiang1994/blog-demo.git"&gt;https://github.com/weijiang1994/blog-demo.git&lt;/a&gt;&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/51/" rel="alternate"/>
  </entry>
  <entry>
    <id>50</id>
    <title>[Python]PIL图片添加文本时自动换行解决方案</title>
    <updated>2023-09-27T02:08:19.929754+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="1. 使用PIL给图片添加文本" name="1. 使用PIL给图片添加文本"&gt;&lt;/a&gt;1. 使用PIL给图片添加文本&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;如简介中所述,PIL可以方便的在图片上添加文本,一般都是通过PIL中ImageFont类实现该功能,下面的示例代码就是简单的在图片上添加两段文本。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from PIL import Image, ImageFont, ImageDraw&#13;
&#13;
texts = ['燕子去了,有再来的时候;杨柳枯了,有再青的时候;桃花谢了,有再开的时候。但是,聪明的,你告诉我,我们的日子为什么一去不复返呢?——是有人偷了他们罢:那是谁?又藏在何处呢?是他们自己逃走了罢:现在又到了哪里呢?',&#13;
         '我不知道他们给了我多少日子;但我的手确乎是渐渐空虚了。在默默里算着,八千多日子已经从我手中溜去;像针尖上一滴水滴在大海里,我的日子滴在时间的流里,没有声音,也没有影子。我不禁头涔涔而泪潸潸了。']&#13;
&#13;
picture = 'congcong.png'&#13;
font_size = 24&#13;
font_file = 'simhei.ttf'&#13;
position = [34, 106]&#13;
&#13;
&#13;
def add_text():&#13;
    with Image.open(picture) as im:&#13;
        if im.mode.upper() == 'RGBA':&#13;
            layer = Image.new(&#13;
                'RGBA', im.size, color=(255, 255, 255, 255)&#13;
            )&#13;
            im = Image.alpha_composite(layer, im)&#13;
        draw = ImageDraw.Draw(im)&#13;
&#13;
        font = ImageFont.truetype(font_file, font_size)&#13;
        for text in texts:&#13;
            draw.text(tuple(position), text, font=font, fill='black')&#13;
            position[1] += 25&#13;
        im.save('no_break.png', format='PNG')&#13;
        im.show()&#13;
&#13;
&#13;
add_text()&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上面的示例代码很简单,首先通过&lt;span class="marker"&gt;texts&lt;/span&gt;列表定义了我们需要添加在图片上的文本,然后指定文本字体大小、字体文件路径以及开始添加文本的&lt;span class="marker"&gt;坐标点(x, y)&lt;/span&gt;,因为有些PNG图片是带有透明通道的,因此先判断了图片的模式是否是&lt;span class="marker"&gt;RGBA&lt;/span&gt;模式,如果是则添加透明的涂层,如果不是则直接通过&lt;span class="marker"&gt;ImageDraw.draw()&lt;/span&gt;方法实例化出一个&lt;span class="marker"&gt;draw&lt;/span&gt;对象,这个draw对象就是在图片上添加文本的核心实例对象,通过draw实例中的&lt;span class="marker"&gt;text()&lt;/span&gt;方法将两段文本添加到图片上去,上述代码的运行结果如下图所示。可以看到当需要添加的文本长度过长时,PIL并不会自动进行换行,因此我们需要在文本过长时让其进行自动换行。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/3661image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;如果在你添加的文本中包含有回车符号(\n)PIL是会自动给你进行换行的,但是在实际开发中很多时候我们都是事先不知道文本的具体的长度的!&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="2. 跟字符数截取然后进行换行" name="2. 跟字符数截取然后进行换行"&gt;&lt;/a&gt;2. 跟字符数截取然后进行换行&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在第一节的代码中我们可以清楚的知道在写到第&lt;span class="marker"&gt;21&lt;/span&gt;个字符的时候文本就所占用的像素长度就超过了图片的长度,因此我们可以通过21这个长度作为临界点来截取字符的长度然后换行重新书写,代码如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from PIL import Image, ImageFont, ImageDraw&#13;
&#13;
texts = ['燕子去了,有再来的时候;杨柳枯了,有再青的时候;hello, world,有再开的时候。But, you tell me '&#13;
         ',我们的日子为什么一去不复返呢?——是有人偷了他们罢:那是谁?又藏在何处呢?是他们自己逃走了罢:现在又到了哪里呢?',&#13;
         '我不知道他们给了我多少日子;但我的手确乎是渐渐空虚了。在默默里算着,八千多日子已经从我手中溜去;像针尖上一滴水滴在大海里,我的日子滴在时间的流里,没有声音,也没有影子。我不禁头涔涔而泪潸潸了。']&#13;
&#13;
picture = 'congcong.png'&#13;
font_size = 24&#13;
font_file = 'simhei.ttf'&#13;
position = [34, 106]&#13;
&#13;
&#13;
def add_text():&#13;
    with Image.open(picture) as im:&#13;
        if im.mode.upper() == 'RGBA':&#13;
            layer = Image.new(&#13;
                'RGBA', im.size, color=(255, 255, 255, 255)&#13;
            )&#13;
            im = Image.alpha_composite(layer, im)&#13;
        draw = ImageDraw.Draw(im)&#13;
&#13;
        font = ImageFont.truetype(font_file, font_size)&#13;
        for text in texts:&#13;
            index = 0&#13;
            while index &amp;lt; len(text):&#13;
                draw.text(tuple(position), text[index:index+21], font=font, fill='black')&#13;
                index += 21&#13;
                position[1] += 30&#13;
            position[1] += 20&#13;
        im.show()&#13;
        im.save('break_by_char_count.png', format='PNG')&#13;
&#13;
&#13;
add_text()&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上面的代码跟第一节实例代码类似,只是在添加文本的时候自动截取了字符串的长度,然后重新换行书写,其结果如下图所示,但是我们还是会发现一些问题。如果当字符中包含有中英文的时候,因为中英文在同一种字体中一个字符所占用的长度明显是不一样的,可以看到第二行明显就没有对齐,对于这种情况我们可以使用第三节的解决方案。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/3432image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="3. 使用X轴坐标阈值自动换行" name="3. 使用X轴坐标阈值自动换行"&gt;&lt;/a&gt;3. 使用X轴坐标阈值自动换行&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在PIL的ImageFont库中,可以通过&lt;span class="marker"&gt;font.font.get_size(text)&lt;/span&gt;来获取text文本所占用的像素长度,因此可以通过设置一个X轴的最大阈值来判断文本是否过长,示意图如下。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/2351image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;首先我们通过一些软件(Photo Shop或者GIMP等)获取底图的书写开始坐标以及书写最大X轴坐标,先在代码中设置最大阈值,然后通过getsize()方法获取将要书写的文本的所占用的像素点,我们可以事先设置一个假设的长度,比如这里的21(在实际中可以先测试一下字符的个数然后设置一个理想值)来截取文本的长度,实例代码如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from PIL import Image, ImageFont, ImageDraw&#13;
&#13;
texts = ['燕子去了,有再来的时候;杨柳枯了,有再青的时候;hello, world,有再开的时候。But, you tell me '&#13;
         ',我们的日子为什么一去不复返呢?——是有人偷了他们罢:那是谁?又藏在何处呢?是他们自己逃走了罢:现在又到了哪里呢?',&#13;
         '我不知道他们给了我多少日子;但我的手确乎是渐渐空虚了。在默默里算着,八千多日子已经从我手中溜去;像针尖上一滴水滴在大海里,我的日子滴在时间的流里,没有声音,也没有影子。我不禁头涔涔而泪潸潸了。']&#13;
&#13;
picture = 'congcong.png'&#13;
font_size = 24&#13;
font_file = 'simhei.ttf'&#13;
position = [34, 106]&#13;
max_x = 520&#13;
guess_count = 21&#13;
&#13;
&#13;
def add_text(gc=guess_count):&#13;
    with Image.open(picture) as im:&#13;
        if im.mode.upper() == 'RGBA':&#13;
            layer = Image.new(&#13;
                'RGBA', im.size, color=(255, 255, 255, 255)&#13;
            )&#13;
            im = Image.alpha_composite(layer, im)&#13;
        draw = ImageDraw.Draw(im)&#13;
&#13;
        font = ImageFont.truetype(font_file, font_size)&#13;
        for text in texts:&#13;
            index = 0&#13;
            while index &amp;lt; len(text):&#13;
                while font.font.getsize(text[index: index + gc])[0][0] + position[0] &amp;lt; max_x and index + gc &amp;lt; len(text):&#13;
                    gc += 1&#13;
                draw.text(tuple(position), text[index:index + gc], font=font, fill='black')&#13;
                index += gc&#13;
                position[1] += 30&#13;
                gc = guess_count&#13;
            position[1] += 20&#13;
        im.show()&#13;
        im.save('break_by_char_count.png', format='PNG')&#13;
&#13;
&#13;
add_text()&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上述代码的关键地方在于最大X轴坐标、文本理想长度、以及getsize()方法,通过这三者就可以实现通过像素点进行自动换行了,效果图如下。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/725image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;上面的代码源码都已开源在GitHub点击链接获取&amp;nbsp;&lt;a href="https://github.com/weijiang1994/blog-demo/tree/main/pillow_font_break"&gt;https://github.com/weijiang1994/blog-demo/tree/main/pillow_font_break&lt;/a&gt;&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/50/" rel="alternate"/>
  </entry>
  <entry>
    <id>49</id>
    <title>[Python]web2py的一些不常用的配置记录</title>
    <updated>2023-09-27T02:08:19.929726+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="1. 错误日志配置" name="1. 错误日志配置"&gt;&lt;/a&gt;1. 错误日志配置&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在&lt;span class="marker"&gt;web2py&lt;/span&gt;的根目录中有一个&lt;span class="marker"&gt;examples&lt;/span&gt;的文件夹,其中有一个文件名为&lt;span class="marker"&gt;logging.example.conf&lt;/span&gt;的文件,执行下面的命令将其拷贝到web2py的根目录中。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;cp logging.example.conf ../logging.conf&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;logging.conf文件的内容如下所示&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-ini"&gt;#  Configure the Python logging facility.&#13;
#  To use this file, copy it to logging.conf and edit logging.conf as required.&#13;
#  See http://docs.python.org/library/logging.html for details of the logging facility.&#13;
#  Note that this is not the newer logging.config facility.&#13;
#&#13;
#  The default configuration is console-based (stdout) for backward compatibility;&#13;
#  edit the [handlers] section to choose a different logging destination.&#13;
#&#13;
#  Note that file-based handlers are thread-safe but not mp-safe;&#13;
#  for mp-safe logging, configure the appropriate syslog handler.&#13;
#&#13;
#  To create a configurable logger for application 'myapp', add myapp to&#13;
#  the [loggers] keys list and add a [logger_myapp] section, using&#13;
#  [logger_welcome] as a starting point.&#13;
#&#13;
#  In your application, create your logger in your model or in a controller:&#13;
#&#13;
#  import logging&#13;
#  logger = logging.getLogger("web2py.app.myapp")&#13;
#  logger.setLevel(logging.DEBUG)&#13;
#&#13;
#  To log a message:&#13;
#&#13;
#  logger.debug("You ought to know that %s", details)&#13;
#&#13;
#  Note that a logging call will be governed by the most restrictive level&#13;
#  set by the setLevel call, the [logger_myapp] section, and the [handler_...]&#13;
#  section. For example, you will not see DEBUG messages unless all three are&#13;
#  set to DEBUG.&#13;
#&#13;
#  Available levels: DEBUG INFO WARNING ERROR CRITICAL&#13;
&#13;
[loggers]&#13;
keys=root,rocket,markdown,web2py,rewrite,cron,app,welcome&#13;
&#13;
[handlers]&#13;
keys=consoleHandler,messageBoxHandler,rotatingFileHandler&#13;
#keys=consoleHandler,rotatingFileHandler&#13;
#keys=osxSysLogHandler&#13;
#keys=notifySendHandler&#13;
&#13;
[formatters]&#13;
keys=simpleFormatter&#13;
&#13;
[logger_root]&#13;
level=WARNING&#13;
handlers=consoleHandler,rotatingFileHandler&#13;
&#13;
[logger_web2py]&#13;
level=WARNING&#13;
handlers=consoleHandler,rotatingFileHandler&#13;
qualname=web2py&#13;
propagate=0&#13;
&#13;
#  URL rewrite logging (routes.py)&#13;
#  See also the logging parameter in routes.py&#13;
#&#13;
[logger_rewrite]&#13;
level=WARNING&#13;
qualname=web2py.rewrite&#13;
handlers=consoleHandler,rotatingFileHandler&#13;
propagate=0&#13;
&#13;
[logger_cron]&#13;
level=WARNING&#13;
qualname=web2py.cron&#13;
handlers=consoleHandler,rotatingFileHandler&#13;
propagate=0&#13;
&#13;
# generic app handler&#13;
[logger_app]&#13;
level=WARNING&#13;
qualname=web2py.app&#13;
handlers=consoleHandler,rotatingFileHandler&#13;
propagate=0&#13;
&#13;
# welcome app handler&#13;
[logger_welcome]&#13;
level=WARNING&#13;
qualname=web2py.app.welcome&#13;
handlers=consoleHandler,rotatingFileHandler&#13;
propagate=0&#13;
&#13;
# loggers for legacy getLogger calls: Rocket and markdown&#13;
[logger_rocket]&#13;
level=WARNING&#13;
handlers=consoleHandler,messageBoxHandler,rotatingFileHandler&#13;
qualname=Rocket&#13;
propagate=0&#13;
&#13;
[logger_markdown]&#13;
level=WARNING&#13;
handlers=consoleHandler,rotatingFileHandler&#13;
qualname=markdown&#13;
propagate=0&#13;
&#13;
[handler_consoleHandler]&#13;
class=StreamHandler&#13;
level=WARNING&#13;
formatter=simpleFormatter&#13;
args=(sys.stdout,)&#13;
&#13;
[handler_messageBoxHandler]&#13;
class=gluon.messageboxhandler.MessageBoxHandler&#13;
level=ERROR&#13;
formatter=simpleFormatter&#13;
args=()&#13;
&#13;
[handler_notifySendHandler]&#13;
class=gluon.messageboxhandler.NotifySendHandler&#13;
level=ERROR&#13;
formatter=simpleFormatter&#13;
args=()&#13;
&#13;
# Rotating file handler&#13;
#   mkdir logs in the web2py base directory if not already present&#13;
#   args: (filename[, mode[, maxBytes[, backupCount[, encoding[, delay]]]]])&#13;
#&#13;
[handler_rotatingFileHandler]&#13;
class=handlers.RotatingFileHandler&#13;
level=DEBUG&#13;
formatter=simpleFormatter&#13;
args=("web2py.log", "a", 1000000, 5)&#13;
&#13;
[handler_osxSysLogHandler]&#13;
class=handlers.SysLogHandler&#13;
level=WARNING&#13;
formatter=simpleFormatter&#13;
args=("/var/run/syslog", handlers.SysLogHandler.LOG_DAEMON)&#13;
&#13;
[handler_linuxSysLogHandler]&#13;
class=handlers.SysLogHandler&#13;
level=WARNING&#13;
formatter=simpleFormatter&#13;
args=("/dev/log", handlers.SysLogHandler.LOG_DAEMON)&#13;
&#13;
[handler_remoteSysLogHandler]&#13;
class=handlers.SysLogHandler&#13;
level=WARNING&#13;
formatter=simpleFormatter&#13;
args=(('sysloghost.domain.com', handlers.SYSLOG_UDP_PORT), handlers.SysLogHandler.LOG_DAEMON)&#13;
&#13;
[formatter_simpleFormatter]&#13;
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s&#13;
datefmt=&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;假设你的&lt;span class="marker"&gt;application&lt;/span&gt;名称为&lt;span class="marker"&gt;hello&lt;/span&gt;,在&lt;span class="marker"&gt;logging.conf&lt;/span&gt;文件中添加如下内容,为&lt;span class="marker"&gt;hello&lt;/span&gt;应用开启&lt;span class="marker"&gt;logging&lt;/span&gt;日志记录&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-ini"&gt;[logger_hello]&#13;
level=WARNING&#13;
qualname=web2py.app.hello&#13;
handlers=consoleHandler,rotatingFileHandler&#13;
propagate=0&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;同时在你的&lt;span class="marker"&gt;models&lt;/span&gt;目录的第一个文件中添加下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;import logging&#13;
logger = logging.getLogger('web2py.app.hello')&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;这样你在你的&lt;span class="marker"&gt;controller&lt;/span&gt;中就可以使用如下代码来追踪错误日志了&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;# hello.py&#13;
&#13;
def index():&#13;
    try:&#13;
        user = db(db.auth_user).select()&#13;
    except:&#13;
        import traceback&#13;
        logger.error(traceback.format_exc())&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="2. 配置默认应用" name="2. 配置默认应用"&gt;&lt;/a&gt;2. 配置默认应用&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在&lt;span class="marker"&gt;web2py&lt;/span&gt;的&lt;span class="marker"&gt;applications&lt;/span&gt;目录中默认的会有&lt;span class="marker"&gt;welcome&lt;/span&gt;这个应用,当我们启动服务访问&lt;span class="marker"&gt;http://127.0.0.1:8000&lt;/span&gt;的时候会自动定位到welcome这个application,其实我们可以通过配置来自行修改这个默认应用。进入到web2py的根目录中,新建一个&lt;span class="marker"&gt;routes.py&lt;/span&gt;文件&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;cd /var/web2py&#13;
vim routes.py&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;假设我们需要默认定位的应用为hello,在routes.py文件中输入下面的内容&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;default_application = "hello"&#13;
default_controller = "default"&#13;
default_function = "index"&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;如果你想默认不是跳转到首页,你也可以自行定义&lt;span class="marker"&gt;default_controller&lt;/span&gt;以及&lt;span class="marker"&gt;default_function&lt;/span&gt;两个参数。个人认为这个配置一般的是在部署到服务器的时候使用域名访问的时候比较有用,在本地开发测试的时候没有必要去配置。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/49/" rel="alternate"/>
  </entry>
  <entry>
    <id>48</id>
    <title>[系列教程]使用Flask搭建一个校园论坛9-支持Markdown语法</title>
    <updated>2023-09-27T02:08:19.929698+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="1. python-markdown" name="1. python-markdown"&gt;&lt;/a&gt;1. python-markdown&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;&lt;span class="marker"&gt;python&lt;/span&gt;的之所以变得非常的流行,其最强大的地方就是拥有很多第三库,通过第三方库可以快速的实现很多功能,论坛评论&lt;span class="marker"&gt;markdown&lt;/span&gt;语法的支持就是通过&lt;span class="marker"&gt;python-markdown&lt;/span&gt;库实现的,本节展示一下&lt;span class="marker"&gt;python-markdown&lt;/span&gt;库的基本用法。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;首先通过&lt;span class="marker"&gt;pip&lt;/span&gt;命令进行安装&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;pip install markdown&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;最简单的&lt;span class="marker"&gt;markdown&lt;/span&gt;转换为&lt;span class="marker"&gt;html&lt;/span&gt;代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from markdown import markdown&#13;
&#13;
md = "## 标题2"&#13;
html = markdown(md)&#13;
print(html)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;我们只需要将要转换的&lt;span class="marker"&gt;markdown&lt;/span&gt;语法的内容传递给&lt;span class="marker"&gt;markdown.markdown()&lt;/span&gt;方法,就会转换成相应的&lt;span class="marker"&gt;html&lt;/span&gt;代码,上述代码的输出结果是&lt;span class="marker"&gt;&amp;lt;h2&amp;gt;标题2&amp;lt;/h2&amp;gt;&lt;/span&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;markdown这个库的生态十分完整,很多常用的功能它都自己内部解决了,如果不能自行解决,在markdown()方法中还可以通过extensions位置参数提供不同的扩展,下面的代码就是通过TOC扩展实现自动添加目录的功能&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from markdown import markdown&#13;
&#13;
md = """[TOC]&#13;
## 1. 标题1&#13;
我的祖国&#13;
&#13;
### 1. 湖南&#13;
我的家乡在湖南&#13;
"""&#13;
html = markdown(md, extensions=['markdown.extensions.toc'])&#13;
print(html)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上面的代码输出如下图所示,通过TOC扩展,就可以实现自动生成目录的功能&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/9739image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;上面的代码中的TOC拓展渲染出来的ul没有样式,如果我们想要自定义对应元素的样式该怎么办呢?我们可以自定义我们的扩展,如下面的代码片段所示&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from markdown import extensions&#13;
from markdown.treeprocessors import Treeprocessor&#13;
&#13;
&#13;
class MyMDStyleTreeProcessor(Treeprocessor):&#13;
    def run(self, root):&#13;
        for child in root.iter():&#13;
            if child.tag == 'ul':&#13;
                child.set('class', 'ul-content')&#13;
            elif child.tag == 'li':&#13;
                child.set('class', 'li-content-text')&#13;
        return root&#13;
&#13;
&#13;
class MyMDStyleExtension(extensions.Extension):&#13;
    def extendMarkdown(self, md):&#13;
        md.registerExtension(self)&#13;
        self.processor = MyMDStyleTreeProcessor()&#13;
        self.processor.md = md&#13;
        self.processor.config = self.getConfigs()&#13;
        md.treeprocessors.add('mystyle', self.processor, '_end')&#13;
&#13;
&#13;
md = """[TOC]&#13;
## 1. 标题1&#13;
我的祖国&#13;
&#13;
### 1. 湖南&#13;
我的家乡在湖南&#13;
"""&#13;
html = markdown(md, extensions=['markdown.extensions.toc', MyMDStyleExtension()])&#13;
print(html)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在上面的代码中,首先通过继承&lt;span class="marker"&gt;Treeprocessor&lt;/span&gt;类,通过判断节点的属性是否为&lt;span class="marker"&gt;ul或者li&lt;/span&gt;来给节点添加上对应的&lt;span class="marker"&gt;class&lt;/span&gt;属性,之后通过继承&lt;span class="marker"&gt;Extension&lt;/span&gt;类,将我们自定义的&lt;span class="marker"&gt;extension&lt;/span&gt;注册到&lt;span class="marker"&gt;markdown&lt;/span&gt;文本上去,上述代码输出如下图所示,可以看到在ul和li标签分别添加上了对应的class属性,在html页面中我们就可以通过设置class属性的css来调整目录的样式了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/1327image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="2. 预览评论" name="2. 预览评论"&gt;&lt;/a&gt;2. 预览评论&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;有了上面一节的基础后就可以开始实现评论markdown语法支持了,在评论区域使用的是两个TAB,第一个TAB是输入评论内容的,第二TAB是预览所输入的评论被渲染后的样式(参照github设计),原始markdown内容如下图所示&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/473image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;点击预览渲染后的样式如下图所示&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/1965image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;评论内容是支持代码内容渲染的,在&lt;span class="marker"&gt;extensions&lt;/span&gt;位置参数中添加&lt;code&gt;extensions=[&amp;#39;markdown.extensions.codehilite&amp;#39;]&lt;/code&gt;即可,但是该扩展依赖于&lt;span class="marker"&gt;Pygments&lt;/span&gt;,所以我们使用之前应该先安装&lt;span class="marker"&gt;Pygments&lt;/span&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;pip install pygments&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;安装完成之后我们可以使用&lt;code&gt;pygmentize -S default -f html -a .codehilite &amp;gt; styles.css&lt;/code&gt; 命令导出代码高亮样式,&lt;span class="marker"&gt;Pygments&lt;/span&gt;支持变成语言以及代码样式非常多,可以访问&lt;a href="https://pygments.org/demo/#"&gt;https://pygments.org/demo/#&lt;/a&gt;进行选择,然后通过前面的命令在命令行中导出相应的样式css文件。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;完成上述步骤之后,我们就可以自定义我们的markdown扩展了,自定义代码片段如下所示&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class MyMDStyleTreeProcessor(Treeprocessor):&#13;
    def run(self, root):&#13;
        for child in root.getiterator():&#13;
            if child.tag == 'table':&#13;
                child.set("class", "table table-bordered table-hover")&#13;
            elif child.tag == 'img':&#13;
                child.set("class", "img-fluid d-block img-pd10")&#13;
            elif child.tag == 'blockquote':&#13;
                child.set('class', 'blockquote-comment')&#13;
            elif child.tag == 'p':&#13;
                child.set('class', 'mt-0 mb-0 p-break')&#13;
            elif child.tag == 'pre':&#13;
                child.set('class', 'mb-0')&#13;
            elif child.tag == 'h1':&#13;
                child.set('class', 'comment-h1')&#13;
            elif child.tag == 'h2':&#13;
                child.set('class', 'comment-h2')&#13;
            elif child.tag == 'h3':&#13;
                child.set('class', 'comment-h3')&#13;
            elif child.tag in ['h4', 'h5', 'h6']:&#13;
                child.set('class', 'comment-h4')&#13;
        return root&#13;
&#13;
&#13;
# noinspection PyAttributeOutsideInit&#13;
class MyMDStyleExtension(extensions.Extension):&#13;
    def extendMarkdown(self, md):&#13;
        md.registerExtension(self)&#13;
        self.processor = MyMDStyleTreeProcessor()&#13;
        self.processor.md = md&#13;
        self.processor.config = self.getConfigs()&#13;
        md.treeprocessors.add('mystyle', self.processor, '_end')&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;与第一节给ul以及li标签添加class属性一致,通过上面的代码可以自定义一些标签的相关样式。有些小伙伴就比较好奇,为什么不用定义代码高亮的样式呢?因为codehilite扩展会自动将我们的代码转换成对应的html,不同的代码内容自动添加不同class,然后通过前面导出的样式文件实现代码高亮,所以这里不需要我们做任何处理。完成了自定义扩展之后,就是后端视图函数的编写了,代码片段如下。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@normal_bp.route('/comment/render-md/', methods=['POST'])&#13;
@login_required&#13;
def render_md():&#13;
    md = request.form.get('md')&#13;
    html = to_html(md)&#13;
    return jsonify({'html': html})&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上面代码片段逻辑应该比较清楚了,就是获取前端传过来的原始md数据,然后通过to_html方式转换成html,然后返回给前端进行渲染,其中to_html()方法代码如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;def to_html(raw):&#13;
    allowed_tags = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'a', 'abbr', 'b', 'br', 'blockquote', 'code', 'del', 'div',&#13;
                    'em', 'img', 'p', 'pre', 'strong', 'span', 'ul', 'li', 'ol']&#13;
    allowed_attributes = ['src', 'title', 'alt', 'href', 'class']&#13;
    html = markdown(raw, output_format='html',&#13;
                    extensions=['markdown.extensions.fenced_code',&#13;
                                'markdown.extensions.codehilite',&#13;
                                'markdown.extensions.tables', MyMDStyleExtension()])&#13;
    clean_html = clean(html, tags=allowed_tags, attributes=allowed_attributes)&#13;
    img_url = '&amp;lt;img class="img-emoji" src="/static/emojis/{}" title="{}" alt="{}"&amp;gt;'&#13;
    for i in EMOJI_INFOS:&#13;
        for ii in i:&#13;
            emoji_url = img_url.format(ii[0], ii[1], ii[1])&#13;
            clean_html = re.sub(':{}:'.format(ii[1]), emoji_url, clean_html)&#13;
    return linkify(clean_html)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在&lt;span class="marker"&gt;to_html()&lt;/span&gt;方法中又出现了一下新的内容&lt;span class="marker"&gt;clean()&lt;/span&gt;以及&lt;span class="marker"&gt;linkify()&lt;/span&gt;两个方法,这两个方法都来字&lt;span class="marker"&gt;bleach&lt;/span&gt;库中,关于&lt;span class="marker"&gt;bleach&lt;/span&gt;库这里不做过多的介绍,感兴趣的童鞋可以自己搜索相关文档了解,这里只介绍使用&lt;span class="marker"&gt;clean()&lt;/span&gt;以及&lt;span class="marker"&gt;linkify()&lt;/span&gt;的目的。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在使用&lt;span class="marker"&gt;clean&lt;/span&gt;之前我们定义了&lt;span class="marker"&gt;allowed_tags&lt;/span&gt;以及&lt;span class="marker"&gt;allowed_attributes&lt;/span&gt;两个列表,懂html的童鞋应该都知道这些事HTML的一些标签以及相应的标签属性,然后通过markdown方法将原始内容转换为HTML,然后使用clean方法,clean方法在这里的作用就是将不再&lt;span class="marker"&gt;allowed_tags&lt;/span&gt;以及&lt;span class="marker"&gt;allowed_attributes&lt;/span&gt;中的属性过滤掉。因为在提交评论的时候,一些不安好心的&amp;#39;良民&amp;#39;可能会在评论中添加一些恶意的&lt;span class="marker"&gt;JavaScript代码&lt;/span&gt;,就是我们所说的&lt;span class="marker"&gt;XSS攻击&lt;/span&gt;,通过clean方法就可以有效的防止这种攻击。linkify()方法的作用是将一些url转换为对应的HTML元素,例如下面的代码片段&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from bleach import linkify&#13;
&#13;
url = "链接: https://2dogz.cn"&#13;
print(linkify(url))&#13;
mail = "邮箱: 804022023@qq.com"&#13;
print(linkify(mail, parse_email=True))&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;输出如下图&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/1697image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;到此为止就完成了评论预览的功能了。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="3.渲染评论并保存" name="3.渲染评论并保存"&gt;&lt;/a&gt;3.渲染评论并保存&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;用户在提交评论后,我们就可以通过上面的方式将评论渲染成HTML内容,然后保存到数据库中去,代码片段如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@post_bp.route('/post-comment/', methods=['POST'])&#13;
@login_required&#13;
@statistic_traffic(db, CommentStatistic)&#13;
def post_comment():&#13;
    ...&#13;
    comment_content = to_html(comment_content)&#13;
    com = Comments(body=comment_content, post_id=post_id, author_id=current_user.id)&#13;
    ...&#13;
    return jsonify({'tag': 1})&#13;
&#13;
&#13;
@post_bp.route('/reply-comment/', methods=['POST'])&#13;
@login_required&#13;
@statistic_traffic(db, CommentStatistic)&#13;
def reply_comment():&#13;
    ...&#13;
    comment = to_html(comment)&#13;
    post_id = request.form.get('post_id')&#13;
    reply = Comments(body=comment, replied_id=comment_id, author_id=current_user.id, post_id=post_id)&#13;
    ...&#13;
    return jsonify({'tag': 1})&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;内容基本与上一节中的添加评论代码一致,只是在其中添加了to_html()方法,将原始的评论内容转换为HTML内容然后保存到数据库中去。&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;博客文章中的代码只截取了部分关键代码,很多细节代码没有贴出来,如果想要在本地机器上运行调试可以访问我的&lt;a href="https://github.com/weijiang1994/university-bbs"&gt;github仓库&lt;/a&gt;或者&lt;a href="https://gitee.com/weiijang/university-bbs"&gt;gitee仓库&lt;/a&gt;仓库,如果想要看到实际效果请访问&lt;a href="http://bbs.2dogz.cn/"&gt;二狗学院&lt;/a&gt;!&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/48/" rel="alternate"/>
  </entry>
  <entry>
    <id>47</id>
    <title>[Python]Flask模板全局变量以及自定义过滤器</title>
    <updated>2023-09-27T02:08:19.929669+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="1. 模板全局变量" name="1. 模板全局变量"&gt;&lt;/a&gt;1. 模板全局变量&lt;/h1&gt;&#13;
&#13;
&lt;h2&gt;1.1 current_user&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;使用过&lt;span class="marker"&gt;Flask-Login&lt;/span&gt;的同学应该都知道,在他的扩展中有一个属性&lt;span class="marker"&gt;current_user&lt;/span&gt;用来保存当前登录用户的相关信息,比如登录状态、登录信息等。而且这个&lt;span class="marker"&gt;current_user&lt;/span&gt;属性除了能在我们后端的python代码中使用外,还可以在任何模板中进行使用,如下面的代码片段&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;在python后端代码中使用&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;def test():&#13;
    ...&#13;
    # 在python后端代码中使用    &#13;
    if current_user.is_authenticated:&#13;
        li = LoveInfo(user=current_user.username, user_ip=remote_ip)&#13;
    else:&#13;
        li = LoveInfo(user='Anonymous', user_ip=remote_ip)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;在html模板文件中使用 &lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;{% if not current_user.is_authenticated %}&#13;
&amp;lt;ul class="navbar-nav f-17"&amp;gt;&#13;
    &amp;lt;li class="nav-item"&amp;gt;&amp;lt;a class="nav-link nav-text" href="/auth/login/"&amp;gt;登录&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&#13;
    &amp;lt;li class="nav-item"&amp;gt;&amp;lt;a class="nav-link nav-text" href="/auth/register/"&amp;gt;注册&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&#13;
&amp;lt;/ul&amp;gt;&#13;
{% else %}&#13;
    ...&#13;
{% endif %}&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在python代码中还比较好理解,当我们需要使用的时候,直接通过import导入即可,但是为什么&lt;span class="marker"&gt;current_user&lt;/span&gt;能够在任意模板文件中使用呢?我们可以通过分析Flask-Login的源码来理解为何可以在任意模板文件中使用。一般的,当我们使用Flask-Login的时候都是通过实例化&lt;span class="marker"&gt;LoginManager&lt;/span&gt;示例,然后通过&lt;span class="marker"&gt;instance.init_app(app, *args)&lt;/span&gt;来注册实例。我们可以进入LoginManager源码,&lt;span class="marker"&gt;init_app()&lt;/span&gt;函数代码如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;def init_app(self, app, add_context_processor=True):&#13;
    '''&#13;
    Configures an application. This registers an `after_request` call, and&#13;
    attaches this `LoginManager` to it as `app.login_manager`.&#13;
&#13;
    :param app: The :class:`flask.Flask` object to configure.&#13;
    :type app: :class:`flask.Flask`&#13;
    :param add_context_processor: Whether to add a context processor to&#13;
        the app that adds a `current_user` variable to the template.&#13;
        Defaults to ``True``.&#13;
    :type add_context_processor: bool&#13;
    '''&#13;
    app.login_manager = self&#13;
    app.after_request(self._update_remember_cookie)&#13;
&#13;
    if add_context_processor:&#13;
        app.context_processor(_user_context_processor)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;可以看到&lt;span class="marker"&gt;add_context_processor&lt;/span&gt;变量的注释&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&lt;em&gt;Whether to add a context processor to &lt;/em&gt;&lt;em&gt;the app that adds a `current_user` variable to the template. &lt;/em&gt;&lt;em&gt;Defaults to ``True``.&lt;/em&gt;&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;翻译为中文的大概意思就是,是否向应用程序添加上下文处理器,以将 &lt;span class="marker"&gt;current_user&lt;/span&gt; 变量添加到模板,默认为True。继续看if语句里面的代码,&lt;span class="marker"&gt;_user_context_processor&lt;/span&gt;是一个方法,其代码如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;def _user_context_processor():&#13;
    return dict(current_user=_get_user())&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;可以看到函数就是返回了一个字典对象,可以发现字典的键就是&lt;span class="marker"&gt;current_user&lt;/span&gt;。为什么&lt;span class="marker"&gt;current_user&lt;/span&gt;可以使用在任意模板中,就是通过&lt;span class="marker"&gt;app.context_processor Api&lt;/span&gt;添加了一个应用程序上下文函数来实现,在Flask的文档中描述如下图所示,翻译过来的意思就是&lt;strong&gt;注册一个模板上下文处理器函数。&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/8168%E5%9B%BE%E7%89%87.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;1.2 使用&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;&lt;span class="marker"&gt;context_processor&lt;/span&gt;的使用方法有两种,如下代码所示,分别使用装饰器的方式定义模板全局变量以及方法,已函数的形式定义模板全局变量。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask import Flask&#13;
import datetime&#13;
&#13;
app = Flask(__name__)&#13;
&#13;
&#13;
# use decorator&#13;
@app.context_processor()&#13;
def cur_day():&#13;
    return datetime.date.today()&#13;
&#13;
&#13;
# context function &#13;
@app.context_processor()&#13;
def get_uuid():&#13;
    import uuid&#13;
&#13;
    def _uuid():&#13;
        return uuid.uuid3()&#13;
&#13;
    return dict(uuid=_uuid)&#13;
&#13;
&#13;
def current_time():&#13;
    return dict(current_time=datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))&#13;
&#13;
&#13;
# use context_processor function&#13;
app.context_processor(current_time)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="2.自定义过滤器" name="2.自定义过滤器"&gt;&lt;/a&gt;2.自定义过滤器&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;Flask已经内置了很多过滤器,比如&lt;span class="marker"&gt;&lt;em&gt;truncate&lt;/em&gt;、&lt;/span&gt;&lt;em&gt;&lt;span class="marker"&gt;length&lt;/span&gt;等,&lt;/em&gt;除了内置的过滤器之外,我们还可以自定义Jinja2模板的过滤器。在Flask中有两种方法自定义过滤器,代码如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@app.template_filter('reverse')&#13;
def reverse_filter(s):&#13;
    return s[::-1]&#13;
&#13;
def reverse_filter(s):&#13;
    return s[::-1]&#13;
app.jinja_env.filters['reverse'] = reverse_filter&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;第一种方法是使用&lt;span class="marker"&gt;app.template_filter()&lt;/span&gt;装饰器来实现,装饰器中的参数,就是后续可以在Jinja2模板文件中使用的过滤器名称;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;第二种方法是通过&lt;span class="marker"&gt;app.jinja_env.filters&lt;/span&gt;将过滤器函数存入字典中,字典的键就是Jinja2模板文件中使用的过滤器名称;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;示例代码链接:&lt;a href="https://github.com/weijiang1994/flask-api-test"&gt;https://github.com/weijiang1994/flask-api-test&lt;/a&gt;&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/47/" rel="alternate"/>
  </entry>
  <entry>
    <id>46</id>
    <title>[Linux]SSH遭遇暴力破解简单解决方案</title>
    <updated>2023-09-27T02:08:19.929642+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="1.事件复盘" name="1.事件复盘"&gt;&lt;/a&gt;1.事件复盘&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;今早登录服务器,想查看一下&lt;span class="marker"&gt;gunicorn&lt;/span&gt;的运行日志,阴差阳错的打开了log目录下的&lt;span class="marker"&gt;auth.log&lt;/span&gt;日志文件,之前不是很了解这个日志文件的作用是啥,文件里面包含了大量的&lt;span class="marker"&gt;sshd&lt;/span&gt;登录失败日志,而且登录IP都不是我经常使用的登录IP,sshd登录失败日志如下图所示&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/5455image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;通过&lt;a href="https://2dogz.cn/tool/query-ip/"&gt;IP查询工具&lt;/a&gt;去查看这些登录IP的所在地,发现很多都是国外的,当然也存在一些国内的IP,我就很纳闷了,干点什么事情不好,为什么偏偏要搞这些东西?之后通过lastb命令查看登录失败日志,如下图,&lt;span class="marker"&gt;lastb&lt;/span&gt;命令是通过读取&lt;span class="marker"&gt;/var/log/btmp&lt;/span&gt;文件,去获取ssh登录失败日志,具体的使用方法可以自行百度,这里不详细说明。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/7415image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;2.定时脚本监听日志&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;既然通过日志文件可以查看到ssh登录失败的IP,那么我们就可以通过解析日志获取登录失败的IP,然后将这些IP加入到黑名单中,就可以有效的防止暴力破解登录密钥了。在Linux系统中通过/etc目录中的&lt;span class="marker"&gt;hosts.allow&lt;/span&gt;与&lt;span class="marker"&gt;hosts.deny&lt;/span&gt;两个文件来配置允许以及拒绝登录主机的IP,因此我们只需要通过解析&lt;span class="marker"&gt;/var/log/auth.log&lt;/span&gt;日志文件中的IP,然后将IP加入到&lt;span class="marker"&gt;hosts.deny&lt;/span&gt;文件中,这样在hosts.deny文件中的IP如果再视图连接登录我们的主机,就会出现connect refuse,通过下面的shell脚本来解析auth.log日志&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;#! /bin/bash&#13;
cat /var/log/auth.log|awk '/Failed/{print $(NF-3)}'|sort|uniq -c|awk '{print $2"="$1;}' &amp;gt; /usr/local/bin/black.list&#13;
for i in `cat  /usr/local/bin/black.list`&#13;
do&#13;
  IP=`echo $i |awk -F= '{print $1}'`&#13;
  NUM=`echo $i|awk -F= '{print $2}'`&#13;
  if [ ${#NUM} -gt 1 ]; then&#13;
    grep $IP /etc/hosts.deny &amp;gt; /dev/null&#13;
    if [ $? -gt 0 ];then&#13;
      echo "sshd:$IP:deny" &amp;gt;&amp;gt; /etc/hosts.deny&#13;
    fi&#13;
  fi&#13;
done&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上面的脚本十分简单,就是通过解析auth.log文件,通过失败登录次数来判断是否将其加入到&lt;span class="marker"&gt;/etc/hosts.deny&lt;/span&gt;文件中。之后我们可以将此脚本设置进定时任务中,定时执行脚本,刷新hosts.deny中的数据,关于定时任务的设置可以查看&lt;a href="https://2dogz.cn/blog/article/7/"&gt;使用crontab定时备份数据库&lt;/a&gt;博客。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;3.更改ssh登录端口号&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;默认的我们的Linux主机的&lt;span class="marker"&gt;ssh登录端口号一般为22&lt;/span&gt;,我们将端口号修改端口号之后,可以大幅的减少他人暴力破解登录我们主机频率,因为如果ssh所连接的端口号错误的话,会直接返回Connection refused的连接拒绝信息。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;ssh的的配置信息在&lt;span class="marker"&gt;/etc/ssh&lt;/span&gt;目录中的&lt;span class="marker"&gt;sshd_config&lt;/span&gt;文件,通过如下的命令可以修改ssh的连接端口号&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak&#13;
sudo vim /etc/ssh/sshd_config&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;修改文件内容,把22端口关闭,然后改为其他的端口&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-nginx"&gt;Port 7895&#13;
Port 5555&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上述内容就是将端口号改为&lt;span class="marker"&gt;7895&lt;/span&gt;和&lt;span class="marker"&gt;5555&lt;/span&gt;,修改完后保存文件,然后通过下面的命令重启ssh服务&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo service ssh restart&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;然后通过远程主机进行连接,通过上面修改的端口号。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;4.禁用密码登录&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在日常开发中很多人都是通过ssh登录密钥进行ssh远程登录,其实如果黑客知道了你的主机密钥,那么他也可以通过密码远程连接你的主机,然后对你的主机造成不必要的破坏,甚至勒索等等。ssh除了通过密码登录之外还可以通过&lt;span class="marker"&gt;PublicKey&lt;/span&gt;登录,其实就是将你们需要访问主机的目标机器的&lt;span class="marker"&gt;id_ras.pub&lt;/span&gt;内容添加到你的主机&lt;span class="marker"&gt;know_hosts&lt;/span&gt;文件中去,具体操作方法可以参考&lt;a href="https://2dogz.cn/blog/article/25/"&gt;使用ssh时的一些骚操作&lt;/a&gt;。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;同样的修改配置文件的内容,就可以关闭密码登录,只能通过PublicKey认证登录了,修改完之后记得保存文件,然后重启ssh服务。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-nginx"&gt;PasswordAuthentication no&#13;
ChallengeResponseAuthentication no&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;&lt;strong&gt;进行此项操作之前请务必先将你个人电脑的Publickey添加到你需要登录的主机中去,进行测试,如果成功免密登录,在进行此项设置,以防登录不了主机!!!切记!!!!!&lt;/strong&gt;&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;h1&gt;5.小记&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;暴露在公网的服务器请务必注意安全!上面的方案其实最有效可行的是修改ssh的登录端口,在我修改端口之后,就没有出现过类似的日志信息了!!!&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/46/" rel="alternate"/>
  </entry>
  <entry>
    <id>45</id>
    <title>[Linux]Shell脚本相关记录</title>
    <updated>2023-09-27T02:08:19.929614+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="1.参数传递" name="1.参数传递"&gt;&lt;/a&gt;1.参数传递&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在使用shell脚本的时候,经常会遇到参数需要根据输入进行变化的需求,shell脚本传递参数十分简单,直接在后面接参数即可,然后在脚本中通过&lt;span class="marker"&gt; $index&lt;/span&gt; 来获取参数的值,如下面的例子所示,shell脚本的内容如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;#/bin/bash&#13;
# test.sh&#13;
&#13;
echo "参数1的值为:"$1&#13;
echo "参数2的值为:"$2&#13;
echo "参数3的值为:"$3&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在终端中执行下面的命令&lt;/p&gt;&#13;
&#13;
&lt;div class="m-block-text" style="background:#eeeeee; border:1px solid #cccccc; padding:5px 10px"&gt;./test.sh 12 34 56&lt;/div&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;输出如下&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/9363%E5%9B%BE%E7%89%87.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;我们发现参数序号是从已开始计算的,这有点不符合计算机的常规逻辑从0开始计算,其实不然,&lt;span class="marker"&gt;$0&lt;/span&gt;实际上是用来表示了我们的&lt;span class="marker"&gt;shell脚本文件的名称&lt;/span&gt;了,shell脚本代码修改如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;#!/bin/bash&#13;
# test.sh&#13;
&#13;
echo "参数0的值为:"$0&#13;
echo "参数1的值为:"$1&#13;
echo "参数2的值为:"$2&#13;
echo "参数3的值为:"$3&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;然后再执行脚本,发现输出如下&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/6837%E5%9B%BE%E7%89%87.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;上述的输出结果验证了$0是用来保存我们的shell文件名!&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="2.参数名传递" name="2.参数名传递"&gt;&lt;/a&gt;2.参数名传递&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;除了上面的传递参数方式,在我们日常使用Linux系统的过程中,经常会看见很多命令后面可以带命名参数比如 ls -a,同样的shell脚本也可以实现,通过&lt;span class="marker"&gt;getopts函数&lt;/span&gt;来实现这种方式,getopts函数的格式如下所示&lt;/p&gt;&#13;
&#13;
&lt;div class="m-block-text" style="background:#eeeeee; border:1px solid #cccccc; padding:5px 10px"&gt;getopts [option[:]] [DESCPRITION] VARIABLE&lt;/div&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;option:表示为某个脚本可以使用的选项 &amp;quot;:&amp;quot; 冒号如果某个选项(option)后面出现了冒号(&amp;quot;:&amp;quot;),则表示这个选项后面可以接参数(即一段描述信息DESCPRITION) VARIABLE:表示将某个选项保存在变量VARIABLE中&amp;nbsp;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;getopts是linux系统中的一个内置变量,一般用在循环中。每当执行循环时,getopts都会检查下一个命令选项,如果这些选项出现 在option中,则表示是合法选项,否则不是合法选项。并将这些合法选项保存在VARIABLE这个变量中。 getopts还包含两个内置变量,及OPTARG和OPTIND OPTARG 就是将选项后面的参数(或者描述信息DESCPRITION)保存在此变量当中。 OPTIND 这个表示命令行的下一个选项或参数的索引(文件名不算选项或参数)&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;使用下面的示例代码来说明一下getopts的简单用法&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;#!/bin/bash&#13;
# test_param.sh&#13;
&#13;
while getopts ":u:p:d:f:" opt&#13;
do&#13;
	case $opt in&#13;
		u)&#13;
		  echo "参数-u的值为:"$OPTARG;;&#13;
		p)&#13;
		  echo "参数-u的值为:"$OPTARG;;&#13;
		d)&#13;
		  echo "参数-u的值为:"$OPTARG;;&#13;
        f)&#13;
          echo "参数-u的值为:"$OPTARG;;&#13;
	esac&#13;
done&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;执行脚本&amp;nbsp;&lt;span class="marker"&gt;./test_param.sh -u user -p 123 -d bbs -f /data/my-tool/&lt;/span&gt;&amp;nbsp;输出结果如下&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/962image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="3.显示帮助信息" name="3.显示帮助信息"&gt;&lt;/a&gt;3.显示帮助信息&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;我们在使用Linux命令的时,很多命令都支持-h参数输出帮助帮助信息,我们自己定义的shell脚本同样的也可以支持&lt;span class="marker"&gt;-h&lt;/span&gt;参数,shell脚本示例代码如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;help() {&#13;
    echo "脚本参数如下:"&#13;
    echo "    -h : 帮助信息"&#13;
    echo "    -u : 数据库连接用户名"&#13;
    echo "    -p : 数据库登录密码"&#13;
    echo "    -d : 目标数据库名"&#13;
    echo "    -f : 导出sql文件路径"&#13;
    exit 1&#13;
}&#13;
&#13;
if [[ $# == 0 || "$1" == "-h" ]]; then&#13;
    help&#13;
fi&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;首先定义了help()函数,在函数中输出相关的帮助信息,之后通过判断用户输入的参数&lt;span class="marker"&gt;是否为-h或者参数个数是否为零&lt;/span&gt;,如果满足条件则执行help函数,执行脚本 &lt;span class="marker"&gt;./test_param.sh -h&lt;/span&gt; 输出结果如下&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/6426image.png" /&gt;&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/45/" rel="alternate"/>
  </entry>
  <entry>
    <id>44</id>
    <title>[陈词滥调]房子一定要买顶层</title>
    <updated>2023-09-27T02:08:19.929586+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 房子一定要买顶层,房子一定要买顶层,房子一定要买顶层,重要的事情一定要说三遍。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 随着社会经济水平的发展,大部分都开始在城里谋生了,而我就是其中的一员。17年秋招的末尾,一家深圳自动化公司给了我offer,那时候的我不谙世事,无忧无虑,得过且过,活在当下,在正式通知录用我之后,就没有继续找工作了,临近毕业,毕业论文也是当时比较重要的事情,就这样,18年顺利毕业之后,就独自一人来到了深圳&amp;ldquo;闯荡&amp;rdquo;(实际上应该说是流浪)了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 大城市那是&amp;ldquo;针不挫&amp;rdquo;,但是由于个人实力不济,不太可能在深圳定居,于是20年七月份就辞去了深圳的工作回到了长沙了。期间在深圳也租过两次房,且都不是在顶层,第一次是租在城中村中,两室一厅跟前同事&amp;ldquo;小张总&amp;rdquo;合租的,租金2200,这样的价格对于深圳来说算的是十分实惠的,在这里住了半年之后就搬走了,主要是因为城中村的房子太潮湿了,蟑螂随处可见。最让我印象深刻的事情是,有一次我想用我的电饭煲煮面条吃,那个电饭煲盖子还是盖着的,当我揭开盖子的时候我惊呆了,里面有几只小强在那里,顿时就感到十分的恶心,于是乎烧了一壶热水倒进了电饭煲的内胆里,想着用开水泡一下消毒一下应该就可以用了,但后来我还是直接把这个电饭煲给扔掉了,因为实在是太恶心了。在这边租房的其实住的还是挺不错的,楼上楼下、邻里邻居都很安静不吵闹,就是因为蟑螂太多了,住了半年就搬走了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 后来就租的公寓,公寓的环境确实很不错,最主要的是没有蟑螂,唯一的缺点就是房间太小。睡觉的地方刚好能放进一张床铺,然后另外的空间就是厨房跟洗手间了,但是一个人住还是可以了,就是每次女朋友过来都会说我这个是小破屋。这边离深圳宝安国际机场很近,我住在12楼,每天下班后来能上楼顶看飞机,虽然说晚上睡觉的时候会有飞机引擎轰鸣的声音,但是这种噪声属于我感觉还比较催眠,在这边居住环境也非常地舒服,我从来没有听到过楼上有把地板弄得很响的声音,只是偶尔大半夜听到有人进出电梯的响声,但是都不会发出其他声音扰民。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 后来回到了长沙了,由于新东家这边不会提供住宿,就只能选择在外面租房了。内地城市跟沿海城市相比还是差的比较多,这里我都看不到租房的那种小广告,问了一下在这边上班的同学,说是这边房子都是房东托付给中介去租。后来在安居客上找到比较心仪的房源,联系中介看了房,就定下了,最后果然我与房东一人半个月的中介费用给中介(瞬间觉得好坑啊),房子挺大,一室一厅43平米左右,1500一月,水电费、物业费自己缴纳。才搬进来的时候,也没有感觉楼上吵,过了一段时间发现,可能是楼上的房子也是出租出去的,每天晚上12点到2点多,一直听到鞋子哒哒哒的声音,是不是的还能听到狗叫,有时候实在忍无可忍,就拿着拖把棍怼了几下天花板,楼上就安静了,有时候对打开窗户骂几句,也安静了。久而久之我也习惯了这样,晚上就带着耳塞睡觉了。又过了一段时间,感觉隔壁的房子也搬来的新的租户,有一天早上5、6点多的时候,就听到外面很大的声音,咋一听我还以为是在外面吵架,后来才发现,是隔壁这个傻逼女的。摸清楚了她的作息方式,每天晚上6点到7点左右出门,凌晨三四点的时候回来,因为经常我都听到很大声的关门声,打开手机看经常是凌晨三四点左右的时候,我都很好奇她是作什么的。我真是倒了八辈子的血霉,楼上、楼下都是这种玩意,素质教育的漏网之鱼。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 后来女朋友也来长沙这边上班了,由于上班的公司离我住的地方比较远,她就在河西那边租房了。不得不感叹我们的运气真他妈的好,这两天楼上的sb在作妖了,大早上5点多就开始在楼上搞得蹦蹦蹦响,一直不停,我上去敲门想跟它交涉一下,他妈的门都不开,于是女朋友就只站在阳台上说,说了之后好了几分钟,又他妈开始了,我已经打算在咸鱼上买神器了,对付这种人,就应该以暴制暴,这种人怎么不灭绝啊,都2021年了,还有这么多没有公德心的人啊。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 相比于长沙,让我觉得大城市的人口素质确实很高,可能是因为深圳的年轻人居多的原因吧。很奇怪的是,我之前租的房子隔壁与楼上两个玩意应该都是年轻人,隔壁那女的我还见过她,长得人模人样的,没一点素质、公德心可言。现在楼上应该是个大妈,目前为止还没有见过庐山真面目,不知道是人是鬼!!!&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; 买房子一定要买顶层,素质、公德心只能紧握在我们自己的手中,不要对他人抱有任何侥幸心理!!!因为楼上住的是人是狗,你又不知道!!!&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/44/" rel="alternate"/>
  </entry>
  <entry>
    <id>43</id>
    <title>[Python]flask-githubcard简单使用介绍</title>
    <updated>2023-09-27T02:08:19.929559+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="安装依赖" name="安装依赖"&gt;&lt;/a&gt;安装依赖&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;跟其他的python第三方库一样,通过pip的方式就可以自动将其安装在自己的项目环境中,flask-githubcard依赖于flask、requests,在安装过程中会自动进行依赖安装。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;pip install flask-githubcard&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="简单使用" name="简单使用"&gt;&lt;/a&gt;简单使用&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;新建app.py文件,添加下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask import Flask, render_template&#13;
from flask_githubcard import GithubCard&#13;
&#13;
app = Flask(__name__)&#13;
app.config['GITHUB_USERNAME'] = 'weijiang1994'&#13;
app.config['GITHUB_REPO'] = 'Blogin'&#13;
githubcard = GithubCard(app)&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在上面的代码清单中,我们主要是实例化了一个flask实例,同时给其配置了&lt;span class="marker"&gt;GITHUB_USERNAME&lt;/span&gt; 以及 &lt;span class="marker"&gt;GITHUB_REPO&lt;/span&gt; 两个参数,此两个参数就是你要显示目标仓库卡片的相关信息,&lt;span class="marker"&gt;flask-githubcard&lt;/span&gt;支持配置的参数就只有此两个参数,在配置完成后就通过初始化了flask-githubcard实例了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;之后我们在&lt;span class="marker"&gt;templates&lt;/span&gt;目录中新建一个&lt;span class="marker"&gt;index.html&lt;/span&gt;文件,将下面的代码填入到文件中。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;&amp;lt;!DOCTYPE html&amp;gt;&#13;
&amp;lt;html lang="en"&amp;gt;&#13;
&amp;lt;head&amp;gt;&#13;
    &amp;lt;meta charset="UTF-8"&amp;gt;&#13;
    &amp;lt;title&amp;gt;Sample-default theme&amp;lt;/title&amp;gt;&#13;
    {{ githubcard.init_css() }}&#13;
    {{ githubcard.init_js() }}&#13;
&amp;lt;/head&amp;gt;&#13;
&amp;lt;body&amp;gt;&#13;
&amp;lt;div class="container mt-2"&amp;gt;&#13;
    &amp;lt;h4&amp;gt;&amp;lt;b&amp;gt;Flask-GithubCard extension sample.&amp;lt;/b&amp;gt;&amp;lt;/h4&amp;gt;&#13;
    &amp;lt;hr&amp;gt;&#13;
    &amp;lt;a href="/default/"&amp;gt;default&amp;lt;/a&amp;gt;&#13;
    &amp;lt;a href="/darkly/"&amp;gt;darkly&amp;lt;/a&amp;gt;&#13;
    &amp;lt;div class="container mt-2"&amp;gt;&#13;
        &amp;lt;div class="row "&amp;gt;&#13;
            &amp;lt;div class="col-4"&amp;gt;{{ githubcard.generate_card() }}&amp;lt;/div&amp;gt;&#13;
        &amp;lt;/div&amp;gt;&#13;
    &amp;lt;/div&amp;gt;&#13;
&amp;lt;/div&amp;gt;&#13;
&amp;lt;/body&amp;gt;&#13;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在上面的代码中,首先在head section中通过&lt;span class="marker"&gt;init_css()&lt;/span&gt;以及&lt;span class="marker"&gt;init_js()&lt;/span&gt;初始化了依赖文件,flask-githubcard的主要依赖&lt;span class="marker"&gt;bootstrap4&lt;/span&gt;、&lt;span class="marker"&gt;jQuery&lt;/span&gt;以及&lt;span class="marker"&gt;fontawesome&lt;/span&gt;,然后在body section中通过&lt;span class="marker"&gt;generate_card()&lt;/span&gt;函数来完成了github card的渲染。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;回到app.py文件,添加视图函数&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@app.route('/')&#13;
def index():&#13;
    return render_template('index.html')&#13;
&#13;
&#13;
if __name__ == '__main__':&#13;
    app.run()&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;运行app.py文件,访问http://127.0.0.1:5000既可以看到如下图所示的效果&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto pic" src="https://7.dusays.com/2021/06/10/66d2716789d8d.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="参数配置" name="参数配置"&gt;&lt;/a&gt;参数配置&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在第二节中程序示例都采用的是默认的参数,我们可以根据需要配置自己的参数。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;配置css、js依赖&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;默认的css、js依赖是使用的flask-githubcard static下的静态文件,fontawesome使用的jsdelivr的cdn,如果 你想更换的话,可以通过下面的方式来修改&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;&amp;lt;head&amp;gt;&#13;
    {{githubcard.init_css(bootstrap='path/cdn_url', fontawesome='path/cdn_url')}}&#13;
    {{githubcard.init_js(jquery='path/cdn_url', bootstrap='path/cdn_url')}}&#13;
&amp;lt;/head&amp;gt;&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h2&gt;配置主题模式&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;flask-githubcard提供了两种主题模式,分别是default、darkly,可以通过下面的方式来指定&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;&amp;lt;head&amp;gt;&#13;
    {{githubcard.init_css(theme='darkly')}}&#13;
&amp;lt;/head&amp;gt;&#13;
&amp;lt;div&amp;gt;&#13;
    {{githubcard.generate_card(theme='darkly')}}&#13;
&amp;lt;/div&amp;gt;&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;darkly主题模式效果如下图&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto pic" src="https://7.dusays.com/2021/06/10/736fed4674429.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="注意事项" name="注意事项"&gt;&lt;/a&gt;注意事项&lt;/h1&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;由于使用了github的api在没有进行授权的情况下,唯一IP在每小时内限制的访问次数为60次,超过60次则会报403, 如果访问频率过高,请前往github上授权账号;&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;在国内访问github会出现超时的现象,可能会导致网页一直无法打开!&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/43/" rel="alternate"/>
  </entry>
  <entry>
    <id>42</id>
    <title>[Git]git不常用操作记录(2)</title>
    <updated>2023-09-27T02:08:19.929531+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id=".gitignore的使用" name=".gitignore的使用"&gt;&lt;/a&gt;.gitignore的使用&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;.gitignore大部分人都知道如何使用,在项目的根目录创建一个.gitigonre文件,可以在提交代码的时候将一个不需要提交的文件给忽略掉。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;$ cat .gitignore&#13;
*.[oa]&#13;
*~&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;第一行告诉 Git 忽略所有以 .o 或 .a 结尾的文件。一般这类对象文件和存档文件都是编译过程中出现的。 第二行告诉 Git 忽略所有名字以波浪符(~)结尾的文件,许多文本编辑软件(比如 Emacs)都用这样的文件名保存副本。 此外,你可能还需要忽略 log,tmp 或者 pid 目录,以及自动生成的文档等等。 要养成一开始就为你的新仓库设置好 .gitignore 文件的习惯,以免将来误提交这类无用的文件。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;文件 &lt;span class="marker"&gt;.gitignore &lt;/span&gt;的格式规范如下:&lt;br /&gt;&#13;
&amp;bull; 所有空行或者以 # 开头的行都会被 Git 忽略。&lt;br /&gt;&#13;
&amp;bull; 可以使用标准的 glob 模式匹配,它会递归地应用在整个工作区中。&lt;br /&gt;&#13;
&amp;bull; 匹配模式可以以(/)开头防止递归。&lt;br /&gt;&#13;
&amp;bull; 匹配模式可以以(/)结尾指定目录。&lt;br /&gt;&#13;
&amp;bull; 要忽略指定模式以外的文件或目录,可以在模式前加上叹号(!)取反。&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。 星号(*)匹配零个或多个任意字符;[abc] 匹配任何一个列在方括号中的字符 (这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c); 问号(?)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符, 表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)。 使用两个星号(**)表示匹配任意中间目录,比如 a/**/z 可以匹配 a/z 、 a/b/z 或 a/b/c/z 等。&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;看一个简单的栗子&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-ini"&gt;# 忽略所有的 .a 文件&#13;
*.a&#13;
# 但跟踪所有的 lib.a,即便你在前面忽略了 .a 文件&#13;
!lib.a&#13;
# 只忽略当前目录下的 TODO 文件,而不忽略 subdir/TODO&#13;
/TODO&#13;
# 忽略任何目录下名为 build 的文件夹&#13;
build/&#13;
# 忽略 doc/notes.txt,但不忽略 doc/server/arch.txt&#13;
doc/*.txt&#13;
# 忽略 doc/ 目录及其所有子目录下的 .pdf 文件&#13;
doc/**/*.pdf &lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在最简单的情况下,一个仓库可能只根目录下有一个 .gitignore 文件,它递归地应用到整个仓库中。 然而,子目录下也可以有额外的 .gitignore 文件。子目录中的 .gitignore文件中的规则只作用于它所在的目录中。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;github有一个十分详细的针对数十种语言及其项目的.gitignore文件配置,你可以在https://github.com/github/gitignore中找到。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="git log查看提交历史" name="git log查看提交历史"&gt;&lt;/a&gt;git log查看提交历史&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;通过git log命令我们可以查看到当前仓库的所有提交历史记录,它会按照时间倒叙的方式将提交记录显示在屏幕上,如下图所示&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="git log sample" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/4181%E5%9B%BE%E7%89%87.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;当我们在使用git log命令的时候,如果不加上参数的话默认会输出该仓库中所有的提交记录,如果我们需要查看详细的修改信息,我们可以通过&lt;span class="marker"&gt;-p&lt;/span&gt; 或者 &lt;span class="marker"&gt;-patch&lt;/span&gt;参数来获取&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;git log -p&#13;
&#13;
commit 868f4bcb4d3290f4b5320f030fccdf1e7fc8ac8a (HEAD -&amp;gt; main, origin/main, origin/HEAD)&#13;
Merge: 1ee3c37f4 a25cba2c0&#13;
Author: Edward Thomson &amp;lt;ethomson@edwardthomson.com&amp;gt;&#13;
Date:   Mon May 31 21:06:37 2021 +0100&#13;
&#13;
    Merge pull request #5897 from tiennou/fix/coding-style-comment&#13;
&#13;
commit a25cba2c0f4d85dc030c8fd65f5660acf00e9463&#13;
Author: Etienne Samson &amp;lt;samson.etienne@gmail.com&amp;gt;&#13;
Date:   Thu May 27 10:01:55 2021 +0200&#13;
&#13;
    docs: fix incorrect comment marker&#13;
&#13;
diff --git a/docs/coding-style.md b/docs/coding-style.md&#13;
index d5188f0bc..b8b94d69c 100644&#13;
--- a/docs/coding-style.md&#13;
+++ b/docs/coding-style.md&#13;
@@ -172,7 +172,7 @@ tags:&#13;
  *&#13;
  * @param s String to froznicate&#13;
  * @return A newly allocated string or `NULL` in case an error occurred.&#13;
- * /&#13;
+ */&#13;
 char *froznicate(const char *s);&#13;
 ```&#13;
 &#13;
&#13;
commit 1ee3c37f48479e92f57c1a5da8c8393f4a745d13&#13;
Merge: 319ff3490 6b1f6e00b&#13;
Author: Edward Thomson &amp;lt;ethomson@edwardthomson.com&amp;gt;&#13;
Date:   Wed May 19 09:31:30 2021 +0100&#13;
&#13;
    Merge branch 'pr/5853'&#13;
# 省略后续部分&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;如果我们需要限制输出的行数我们可以在后面加上&lt;span class="marker"&gt;-number&lt;/span&gt;[1,2,3,4....]&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;git log -p -2&#13;
&#13;
commit 868f4bcb4d3290f4b5320f030fccdf1e7fc8ac8a (HEAD -&amp;gt; main, origin/main, origin/HEAD)&#13;
Merge: 1ee3c37f4 a25cba2c0&#13;
Author: Edward Thomson &amp;lt;ethomson@edwardthomson.com&amp;gt;&#13;
Date:   Mon May 31 21:06:37 2021 +0100&#13;
&#13;
    Merge pull request #5897 from tiennou/fix/coding-style-comment&#13;
&#13;
commit a25cba2c0f4d85dc030c8fd65f5660acf00e9463&#13;
Author: Etienne Samson &amp;lt;samson.etienne@gmail.com&amp;gt;&#13;
Date:   Thu May 27 10:01:55 2021 +0200&#13;
&#13;
    docs: fix incorrect comment marker&#13;
&#13;
diff --git a/docs/coding-style.md b/docs/coding-style.md&#13;
index d5188f0bc..b8b94d69c 100644&#13;
--- a/docs/coding-style.md&#13;
+++ b/docs/coding-style.md&#13;
@@ -172,7 +172,7 @@ tags:&#13;
  *&#13;
  * @param s String to froznicate&#13;
  * @return A newly allocated string or `NULL` in case an error occurred.&#13;
- * /&#13;
+ */&#13;
 char *froznicate(const char *s);&#13;
 ```&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;如果我们不想看详细的patch只需要显示初略的信息,我们可以通过&lt;span class="marker"&gt;--stat&lt;/span&gt;参数来获取初略提交信息&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;git log --stat&#13;
&#13;
commit 868f4bcb4d3290f4b5320f030fccdf1e7fc8ac8a (HEAD -&amp;gt; main, origin/main, origin/HEAD)&#13;
Merge: 1ee3c37f4 a25cba2c0&#13;
Author: Edward Thomson &amp;lt;ethomson@edwardthomson.com&amp;gt;&#13;
Date:   Mon May 31 21:06:37 2021 +0100&#13;
&#13;
    Merge pull request #5897 from tiennou/fix/coding-style-comment&#13;
&#13;
commit a25cba2c0f4d85dc030c8fd65f5660acf00e9463&#13;
Author: Etienne Samson &amp;lt;samson.etienne@gmail.com&amp;gt;&#13;
Date:   Thu May 27 10:01:55 2021 +0200&#13;
&#13;
    docs: fix incorrect comment marker&#13;
&#13;
 docs/coding-style.md | 2 +-&#13;
 1 file changed, 1 insertion(+), 1 deletion(-)&#13;
&#13;
commit 1ee3c37f48479e92f57c1a5da8c8393f4a745d13&#13;
Merge: 319ff3490 6b1f6e00b&#13;
Author: Edward Thomson &amp;lt;ethomson@edwardthomson.com&amp;gt;&#13;
Date:   Wed May 19 09:31:30 2021 +0100&#13;
&#13;
    Merge branch 'pr/5853'&#13;
&#13;
commit 319ff349039dc73f5fe099f83f198fb65782414f&#13;
Merge: b5dcdad34 1b1e541dd&#13;
Author: Edward Thomson &amp;lt;ethomson@edwardthomson.com&amp;gt;&#13;
Date:   Tue May 18 20:49:40 2021 +0100&#13;
&#13;
    Merge pull request #5892 from libgit2/ethomson/memleak&#13;
    &#13;
    tests: clean up memory leak, fail on leak for win32&#13;
&#13;
commit 6b1f6e00bf7c91b3b5230e20ecb57a7d9792cee7&#13;
Author: Edward Thomson &amp;lt;ethomson@edwardthomson.com&amp;gt;&#13;
Date:   Tue May 18 12:21:15 2021 +0100&#13;
&#13;
    diff: test ignore-blank-lines&#13;
&#13;
 tests/diff/workdir.c | 35 +++++++++++++++++++++++++++++++++++&#13;
 1 file changed, 35 insertions(+)&#13;
# 省略后续部分&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在git log还有一个比较有用的参数是--pretty,可以通过该参数来定制commit log的不同显示模式,例如online、short、full、fuller等。oneline就是将所有的提交记录显示在一行,其他的类似只是详细方式各不相同,最有用的是format参数,他可以定制commit log的显示格式,对应格式如下表所示&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="pretty" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/652%E5%9B%BE%E7%89%87.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;通过下面的命令来显示提交信息的简短hash值、提交作者、提交日期、提交log等&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;git log --pretty=format:"%h - %an, %ar: %s"&#13;
&#13;
868f4bcb4 - Edward Thomson, 7 天前: Merge pull request #5897 from tiennou/fix/coding-style-comment&#13;
a25cba2c0 - Etienne Samson, 11 天前: docs: fix incorrect comment marker&#13;
1ee3c37f4 - Edward Thomson, 3 周前: Merge branch 'pr/5853'&#13;
319ff3490 - Edward Thomson, 3 周前: Merge pull request #5892 from libgit2/ethomson/memleak&#13;
6b1f6e00b - Edward Thomson, 3 周前: diff: test ignore-blank-lines&#13;
1b1e541dd - Edward Thomson, 3 周前: tests: clean up refs::races zero oid test&#13;
a6fb72a8b - Edward Thomson, 3 周前: tests: exit with error on win32 leakcheck&#13;
b5dcdad34 - Edward Thomson, 3 周前: Merge pull request #5852 from implausible/httpclient/skip-entire-body&#13;
3532c5f49 - Edward Thomson, 3 周前: Merge pull request #5850 from punkymaniac/comment-format&#13;
9ccb900d7 - Edward Thomson, 3 周前: Merge pull request #5831 from todaysoftware/bindings/libgit2-delphi&#13;
589443883 - Edward Thomson, 3 周前: Merge branch 'zero_oid_in_old'&#13;
cf323cb9d - Edward Thomson, 3 周前: refs: test git_reference_create_matching failure for zero oid&#13;
be95f684d - Edward Thomson, 4 周前: Merge pull request #5839 from staktrace/find_similar&#13;
# 省略后续内容&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;git log的参数如下图所示&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="git log参数" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/504%E5%9B%BE%E7%89%87.png" /&gt;&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/42/" rel="alternate"/>
  </entry>
  <entry>
    <id>41</id>
    <title>[Python]Flask-Caching的简单应用</title>
    <updated>2023-09-27T02:08:19.929503+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="1.安装Flask-Caching" name="1.安装Flask-Caching"&gt;&lt;/a&gt;1.安装Flask-Caching&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;进入到需要使用到该拓展的项目根目录,通过下面的命令安装。这个拓展是fork &lt;code&gt;Flask-Cache&lt;/code&gt;仓库进行修改的,由于&lt;code&gt;Flask-Cache&lt;/code&gt;已经年久失修了,所以这边推荐使用&lt;code&gt;Flask-Caching&lt;/code&gt;,在安装的时候不要装错了拓展名。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;pip install flask-caching&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="2.简单的使用" name="2.简单的使用"&gt;&lt;/a&gt;2.简单的使用&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;使用一个简单的例子来说明cache的作用以及使用方法&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask import Flask&#13;
from flask_caching import Cache&#13;
&#13;
&#13;
app = Flask(__name__)&#13;
&#13;
@app.route('/no-cache/')&#13;
def no_cache():&#13;
    time.sleep(1)&#13;
    return '没有使用缓存页面'&#13;
&#13;
&#13;
@app.route('/cache/')&#13;
@cache.cached(timeout=10 * 60)&#13;
def cache_page():&#13;
    time.sleep(1)&#13;
    return '使用缓存页面'&#13;
&#13;
&#13;
if __name__ == '__main__':&#13;
    app.run(debug=True)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;启动应用,分别访问no-cache与cache页面,&lt;kbd&gt;F12 &lt;/kbd&gt;打开控制台,查看两个页面的请求时间。&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;打开no-cache页面每次请求时间都要1s以上,因为在视图函数中进行了一秒的延时;&lt;/li&gt;&#13;
	&lt;li&gt;打开cache页面第一次也需要1s以上的时间,但第二次打开就只要毫秒级别的时间了,这就是flask-cache的作用,它将视图函数的结果缓存在字典中(因为使用的是simple方式),当请求过来时候直接去字典中取值然后返回响应;&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="3.更新缓存" name="3.更新缓存"&gt;&lt;/a&gt;3.更新缓存&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;当使用缓存之后,我们会遇到的一个问题,就是数据不会实时刷新,比如我这个博客,如果主页使用了缓存,那么当我新增了文章或者删除了文章,如果没有及时的更新缓存那么访问主页还是获取的原来的数据,因此在必要的时候,我们需要将对应的缓存进行更新。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在我的博客系统中,侧边栏的&lt;code&gt;github&lt;/code&gt;信息以及&lt;code&gt;每日一句&lt;/code&gt;都采用了缓存,核心代码如下所示&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@blog_bp.route('/load-github/', methods=['POST'])&#13;
@cache.cached(timeout=5*60)&#13;
def load_github():&#13;
    theme = request.form.get('theme')&#13;
&#13;
    star = rd.get('star')&#13;
    if star is None:&#13;
        star, fork, watcher, star_dark, fork_dark, watcher_dark, user_info, repo_info = github_social()&#13;
        avatar = user_info.json()['avatar_url']&#13;
        repo_desc = repo_info.json()['description']&#13;
        # 获取浅色主题的shield&#13;
        rd.set('star', star.text)&#13;
        rd.set('fork', fork.text)&#13;
        rd.set('watcher', watcher.text)&#13;
&#13;
        # 获取深色主题的shield&#13;
        rd.set('star_dark', star_dark.text)&#13;
        rd.set('fork_dark', fork_dark.text)&#13;
        rd.set('watcher_dark', watcher_dark.text)&#13;
&#13;
        rd.set('avatar', avatar)&#13;
        rd.set('repo_desc', repo_desc)&#13;
        star = star.text&#13;
&#13;
        if theme == 'dark':&#13;
            star = star_dark.text&#13;
    else:&#13;
        if theme == 'dark':&#13;
            fork = rd.get('fork_dark')&#13;
            watcher = rd.get('watcher_dark')&#13;
            star = rd.get('star_dark')&#13;
        else:&#13;
            fork = rd.get('fork')&#13;
            watcher = rd.get('watcher')&#13;
&#13;
        avatar = rd.get('avatar')&#13;
        repo_desc = rd.get('repo_desc')&#13;
    return jsonify({'star': star,&#13;
                    'fork': fork,&#13;
                    'watcher': watcher,&#13;
                    'avatar': avatar,&#13;
                    'repo_desc': repo_desc&#13;
                    })&#13;
&#13;
&#13;
@blog_bp.route('/load-one/', methods=['POST'])&#13;
@cache.cached(timeout=60*60)&#13;
def load_one():&#13;
    one = rd.get('one')&#13;
    # 防止服务器重启之后清空了redis数据导致前端获取不到当日one内容&#13;
    if not one:&#13;
        one = OneSentence.query.filter_by(day=datetime.date.today()).first()&#13;
        return jsonify({'one': one.content})&#13;
    return jsonify({'one': one})&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在这两个请求的视图函数中,添加了caching的装饰器,每个视图的缓存时间为5分钟,在本地测试之后,感觉功能正常就部署到了生产环境中了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;我的博客有两套主题,一套是light另外一套是dark,其中github shield标签是会根据主题模式进行变化的,但是当我加上了缓存之后,发现切换主题模式之后github shield不会改变,如下所示&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;light主题&lt;/strong&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/6652image.png" /&gt;&lt;strong&gt;dark主题&amp;nbsp;&lt;/strong&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/5322image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;这是回过头来发现,是因为缓存的原因导致没有更新&lt;code&gt;github shield&lt;/code&gt;,于是在切换主题的视图函数中添加了清除缓存的代码,如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@blog_bp.route('/themes/&amp;lt;string:theme_name&amp;gt;/')&#13;
def change_theme(theme_name):&#13;
    if theme_name not in current_app.config['BLOG_THEMES'].keys():&#13;
        abort(404)&#13;
    cache.clear()&#13;
    response = redirect(request.referrer)&#13;
    response.set_cookie('blog_theme', current_app.config['BLOG_THEMES'].get(theme_name), max_age=30 * 24 * 60 * 60)&#13;
    return response&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上面的代码就是在切换主题的时候,&lt;strong&gt;清除了缓存信息&lt;/strong&gt;,这样我们就会重新去获取对应主题的&lt;code&gt;github shield&lt;/code&gt;了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/6151image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;所以在使用数据缓存的时候,我们需要时刻注意保持数据的最新状态。本篇文章就到此了,只是演示了一些简单的&lt;code&gt;Flask-Caching&lt;/code&gt;的应用,感兴趣的可以去查找相关文档或者教程进一步的学习。&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/41/" rel="alternate"/>
  </entry>
  <entry>
    <id>40</id>
    <title>[系列教程]使用Flask搭建一个校园论坛8-帖子评论</title>
    <updated>2023-09-27T02:08:19.929475+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="1.功能简介" name="1.功能简介"&gt;&lt;/a&gt;1.功能简介&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;所有公开的论坛,都会提供用户评论以及用户回复的功能,本节讲述如何实现用户评论帖子、用户回复帖子以及一些其他的功能。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="2.数据表设计" name="2.数据表设计"&gt;&lt;/a&gt;2.数据表设计&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;每篇帖子任何用户都可以在其下方留下评论,因此,帖子对于评论的关系是一个&lt;code&gt;一对多&lt;/code&gt;的关系,如下图所示,这样当我们加载帖子的时候,就可以通过帖子的id获取其全部的评论。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/7514image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在很多论坛中,我们可以看到用户可以在任何帖子下方回复任何评论,每条评论我都们需要发送评论的是谁,同时如果是回复某条评论,我们也需要知道回复的是谁,因此我们可以通过&lt;code&gt;自关联&lt;/code&gt;的方式来实现,评论表的设计如下图所示&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/8284image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;有了上面的思路,我们就可以设计评论表的,在&lt;code&gt;bbs/models.py&lt;/code&gt;模块中加入下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class Comments(db.Model):&#13;
    __tablename__ = 't_comments'&#13;
&#13;
    id = db.Column(db.INTEGER, primary_key=True, autoincrement=True)&#13;
    body = db.Column(db.Text)&#13;
    timestamps = db.Column(db.DATETIME, default=datetime.datetime.now)&#13;
&#13;
    replied_id = db.Column(db.INTEGER, db.ForeignKey('t_comments.id'))&#13;
    author_id = db.Column(db.INTEGER, db.ForeignKey('t_user.id'))&#13;
    post_id = db.Column(db.INTEGER, db.ForeignKey('t_post.id'))&#13;
    delete_flag = db.Column(db.INTEGER, default=0, comment='is it delete? 0: no 1: yes')&#13;
&#13;
    post = db.relationship('Post', back_populates='comments')&#13;
    author = db.relationship('User', back_populates='comments')&#13;
    replies = db.relationship('Comments', back_populates='replied', cascade='all')&#13;
    replied = db.relationship('Comments', back_populates='replies', remote_side=[id])&#13;
&#13;
    def can_delete(self):&#13;
        return self.author_id == current_user.id&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上面的代码中,我们定义了评论表的相关字段,其中delete_flag字段为当前评论的状态,如果等于1则表示当前评论已经被作者删除了。这里的删除是软删除,就是将其状态置为某个标志,并不是真正的在数据库中清除掉。同时我们定义了一个实例函数can_delete此函数的主要作用是用来判断当前评论删除的权限,如果当前评论的作者id等于当前登录用户的id,则可以删除此条评论,反之则不能删除。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="3.评论帖子" name="3.评论帖子"&gt;&lt;/a&gt;3.评论帖子&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;帖子的评论的实现主要是前后端的配合,我们先来实现前端的评论输入。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;a.评论输入框&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;用户需要评论帖子则需要一个评论的入口,所以我们需要在帖子详情页面下方加上用户输入评论的区域,打开&lt;code&gt;bbs/templates/frontend/post/read-post.html&lt;/code&gt;,添加下面的关键代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;&amp;lt;div class="post-div post-comment"&amp;gt;&#13;
    &amp;lt;p id="commentPosition"&amp;gt;&amp;lt;/p&amp;gt;&#13;
    {% if current_user.is_authenticated %}&#13;
        &amp;lt;div&amp;gt;&#13;
            &amp;lt;ul class="nav nav-pills " role="tablist"&amp;gt;&#13;
                &amp;lt;li class="nav-item"&amp;gt;&#13;
                    &amp;lt;a class="nav-link active" data-toggle="pill" href="#addComment"&amp;gt;&amp;lt;i class="fa fa-commenting mr-2"&amp;gt;&amp;lt;/i&amp;gt;评论&amp;lt;/a&amp;gt;&#13;
                &amp;lt;/li&amp;gt;&#13;
                &amp;lt;li class="nav-item"&amp;gt;&#13;
                    &amp;lt;a class="nav-link" onclick="getMarkDownData()" data-toggle="pill" href="#previewComment"&amp;gt;&amp;lt;i class="fa fa-print mr-2"&amp;gt;&amp;lt;/i&amp;gt;预览&amp;lt;/a&amp;gt;&#13;
                &amp;lt;/li&amp;gt;&#13;
            &amp;lt;/ul&amp;gt;&#13;
            &amp;lt;div class="tab-content"&amp;gt;&#13;
                &amp;lt;div id="addComment" class="tab-pane active"&amp;gt;&#13;
                    &amp;lt;textarea onkeydown="tab(this)" class="form-control mt-2 report-textarea" style="height: 150px!important;" id="commentContent" placeholder="请输入评论内容"&amp;gt;&amp;lt;/textarea&amp;gt;&#13;
                    &amp;lt;div class="d-flex mt-1"&amp;gt;&#13;
                        &amp;lt;a class="mb-1 text-decoration-none mr-2 a-link" href="#" id="commentEmoji" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"&amp;gt;&#13;
                            &amp;lt;i class="fa fa-smile-o mr-1"&amp;gt;&amp;lt;/i&amp;gt;表情&#13;
                        &amp;lt;/a&amp;gt;&#13;
                        &amp;lt;div class="dropdown-menu" id="emoji-list" aria-labelledby="commentEmoji"&amp;gt;&#13;
                            {% for emoji_url in emoji_urls %}&#13;
                                &amp;lt;div style="padding:3px"&amp;gt;&#13;
                                    {% for emoji in emoji_url %}&#13;
                                        &amp;lt;button class="btn p-1"&amp;gt;&#13;
                                            &amp;lt;img class="img-emoji"&#13;
                                                 src="{{ url_for('static', filename='emojis/'+emoji[0]) }}"&#13;
                                                 data-toggle="tooltip" data-placement="right"&#13;
                                                 title="{{ emoji.1 }}" alt="{{ emoji.1 }}"&#13;
                                                 data-emoji=":{{ emoji.1 }}:"&amp;gt;&#13;
                                        &amp;lt;/button&amp;gt;&#13;
                                    {% endfor %}&#13;
                                &amp;lt;/div&amp;gt;&#13;
                            {% endfor %}&#13;
                        &amp;lt;/div&amp;gt;&#13;
                        &amp;lt;a onclick="upload()" class="mb-1 text-decoration-none mr-2 a-link span-hand"&amp;gt;&amp;lt;i class="fa fa-photo mr-1"&amp;gt;&amp;lt;/i&amp;gt;图片&amp;lt;/a&amp;gt;&#13;
                        &amp;lt;input type="file" id="uploadInput" onchange="uploadImage()" accept=".png" hidden="hidden"&amp;gt;&#13;
                        &amp;lt;a href="#" class="mb-1 text-decoration-none mr-2 a-link" data-toggle="modal" data-target="#markdownHelp"&amp;gt;&amp;lt;i class="fa fa-file mr-1"&amp;gt;&amp;lt;/i&amp;gt;帮助&amp;lt;/a&amp;gt;&#13;
                        &amp;lt;p class="flex-grow-1 text-right mb-1 p-error-hint"&amp;gt;请输入评论内容!&amp;lt;/p&amp;gt;&#13;
                    &amp;lt;/div&amp;gt;&#13;
                    &amp;lt;div class="d-flex flex-row-reverse"&amp;gt;&#13;
                        &amp;lt;button class="btn btn-info" id="commentBtn" onclick="postComment()"&amp;gt;评论&amp;lt;/button&amp;gt;&#13;
                        &amp;lt;button hidden="hidden" id="replyBtn" onclick="replyComment()" class="btn btn-success mt-2"&amp;gt;回复&amp;lt;/button&amp;gt;&#13;
                        &amp;lt;button hidden="hidden" id="cancleReplyBtn" onclick="cancleReply()" class="btn btn-danger mt-2 mr-2"&amp;gt;取消&amp;lt;/button&amp;gt;&#13;
                        &amp;lt;a id="replyUserP" hidden="hidden" class="p-reply flex-grow-1 text-decoration-none"&amp;gt;&amp;lt;/a&amp;gt;&#13;
                    &amp;lt;/div&amp;gt;&#13;
                &amp;lt;/div&amp;gt;&#13;
                &amp;lt;!-- 评论预览界面 --&amp;gt;&#13;
                &amp;lt;div id="previewComment" class="tab-pane fade"&amp;gt;&#13;
                    &amp;lt;div id="previewHtml" class="mt-2" style="min-height: 50px"&amp;gt;&#13;
&#13;
                    &amp;lt;/div&amp;gt;&#13;
                &amp;lt;/div&amp;gt;&#13;
            &amp;lt;/div&amp;gt;&#13;
        &amp;lt;/div&amp;gt;&#13;
    {% else %}&#13;
        &amp;lt;div class="text-center"&amp;gt;&#13;
            &amp;lt;div class="card-body text-center m-2 m-md-3 f-16" id="no-editor"&amp;gt;&#13;
                &amp;lt;div&amp;gt;您尚未登录,&#13;
                    &amp;lt;a href="/auth/login/"&amp;gt;&amp;lt;span class="badge badge-info"&amp;gt;登录&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt; 或&#13;
                    &amp;lt;a href="/auth/register/"&amp;gt;&amp;lt;span class="badge badge-success"&amp;gt;注册&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt; 后评论&#13;
                &amp;lt;/div&amp;gt;&#13;
            &amp;lt;/div&amp;gt;&#13;
        &amp;lt;/div&amp;gt;&#13;
    {% endif %}&#13;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在帖子的详情页页面,我们就可以看到如下所示的评论输入框&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/93image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;b.处理用户评论与回复&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;在用户在帖子详情页面输入了评论内容之后,点击评论按钮就要将相关参数信息发送到后台服务器中,将这些信息处理、存储在对应的数据库表中,大致流程如下图所示&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/454image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;打开&lt;code&gt;bbs/frontend/post.py&lt;/code&gt;模块中加入下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@post_bp.route('/post-comment/', methods=['POST'])&#13;
@login_required&#13;
@statistic_traffic(db, CommentStatistic)&#13;
def post_comment():&#13;
    comment_content = request.form.get('commentContent')&#13;
    post_id = request.form.get('postId')&#13;
    post = Post.query.get_or_404(post_id)&#13;
 &#13;
    com = Comments(body=comment_content, post_id=post_id, author_id=current_user.id)&#13;
    # 如果评论帖子用户与发帖用户不为同一人则发送消息通知&#13;
    if current_user.id != post.author_id:&#13;
        notice = Notification(target_id=post_id, target_name=post.title, send_user=current_user.username,&#13;
                              receive_id=post.author_id, msg=comment_content)&#13;
        db.session.add(notice)&#13;
    post.update_time = datetime.datetime.now()&#13;
    db.session.add(com)&#13;
    db.session.commit()&#13;
    return jsonify({'tag': 1})&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上面的代码逻辑基本上按照图上的流程所进行的,因为用户可以回复其他用户的评论,在代码中还加入通知被回复用户的逻辑。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;c.提交评论回复&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;在写完后端逻辑之后,我们还需要在帖子详情页面中加入一些js代码来实现与后端的交互,打开&lt;code&gt;bbs/templates/frontend/post/read-post.html&lt;/code&gt;文件,在JavaScript标签块中加入下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-javascript"&gt;// 刷新页面清除保存在sessionStorage中的原始数据&#13;
$(function () {&#13;
    sessionStorage.setItem('md', '');&#13;
})&#13;
&#13;
function cancleReply() {&#13;
    $("#commentBtn").removeAttr('hidden');&#13;
    $("#replyBtn").attr('hidden', 'hidden');&#13;
    $("#cancleReplyBtn").attr('hidden', 'hidden');&#13;
    $("#replyUserP").html('');&#13;
    $("#replyUserP").attr('hidden', 'hidden');&#13;
}&#13;
&#13;
function replyComment() {&#13;
    comment = isEmpty();&#13;
    if (!comment){&#13;
        return false;&#13;
    }&#13;
    commentId = sessionStorage.getItem('commentId');&#13;
    commentUserId = sessionStorage.getItem('commentUserId');&#13;
    postId = $("#postTitle").data('id');&#13;
    $.ajax({&#13;
        type: "post",&#13;
        url: "/post/reply-comment/",&#13;
        data: {"post_id":postId, "comment_id": commentId, "comment_user_id": commentUserId, "comment": comment},&#13;
        success: function (res) {&#13;
            window.location.reload();&#13;
        }&#13;
    })&#13;
}&#13;
&#13;
function reply(commentId, commentUser, commentUserId) {&#13;
    $("#commentBtn").attr('hidden', 'hidden');&#13;
    $("#replyBtn").removeAttr('hidden');&#13;
    $("#cancleReplyBtn").removeAttr('hidden');&#13;
    $("#replyUserP").removeAttr('hidden');&#13;
    $("#replyUserP").html('@'+commentUser);&#13;
    $("#replyUserP").attr('href', '/profile/user/'+commentUserId+'/');&#13;
    sessionStorage.setItem('commentId', commentId);&#13;
    sessionStorage.setItem('commentUserId', commentUserId);&#13;
    $('html,body').animate({ scrollTop: $("#commentPosition").offset().top - 100 }, 200)&#13;
}&#13;
&#13;
// 提交评论&#13;
function postComment() {&#13;
    comment = isEmpty();&#13;
    if (!comment){&#13;
        return false;&#13;
    }&#13;
    let postId = $("#postTitle").data("id");&#13;
    $.ajax({&#13;
        type:"post",&#13;
        data: {"commentContent": comment, 'postId': postId},&#13;
        url: "/post/post-comment/",&#13;
        success: function (res) {&#13;
            window.location.reload();&#13;
        }&#13;
    })&#13;
}&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上面的JavaScript代码主要是用来后端模块进行交互,这里就不进行详细解释,整个帖子的评论系统就完成了。&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;再次提醒!博客文章中的代码只截取了部分关键代码,很多细节代码没有贴出来,如果想要在本地机器上运行调试可以访问我的&lt;a href="https://github.com/weijiang1994/university-bbs"&gt;github仓库&lt;/a&gt;或者&lt;a href="https://gitee.com/weiijang/university-bbs"&gt;gitee仓库&lt;/a&gt;仓库,如果想要看到实际效果请访问&lt;a href="http://bbs.2dogz.cn/"&gt;二狗学院&lt;/a&gt;&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/40/" rel="alternate"/>
  </entry>
  <entry>
    <id>39</id>
    <title>[好文收录]Break the Cycle of Procrastination</title>
    <updated>2023-09-27T02:08:19.929448+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;p&gt;拖延症患者很少什么都不做,他们只是单纯地不做手头上的事。拖延症患者时常会发现他们自己正在打扫房间、喂猫、和阿姨打电话或者陷入一个有趣但是没有任何意义的Youtube未知领域--他们不是什么事情都不做,只是没有做他们自己想做和需要做的事情。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;消除你的拖延症怪圈的第一步就是你要明白为什么你没有做任何你应该做的事情。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;你为什么要拖延?&lt;/h1&gt;&#13;
&#13;
&lt;h3&gt;&lt;strong&gt;&lt;em&gt;&amp;nbsp; &amp;nbsp; &lt;/em&gt;&lt;/strong&gt;拖延,是因为需要完成的事情我感觉过于困难压力太大&lt;/h3&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;分块是你的朋友&#13;
	&lt;ul&gt;&#13;
		&lt;li&gt;把大的事情可以分解成一小块、一小块...&lt;/li&gt;&#13;
		&lt;li&gt;把你的时间分块:每次花在每个项目或者课程上的时间不要超过1-2个小时。&lt;/li&gt;&#13;
		&lt;li&gt;把日常学习分块:每个科目每天学习一点点的知识,这样每个科目都会均匀提升。&lt;/li&gt;&#13;
		&lt;li&gt;制定&lt;a href="https://live-learning-strategies-center.pantheonsite.io/how-to-study/studying-for-and-taking-exams/the-five-day-study-plan/" onclick="window.open(this.href, 'five-day-learn-plan', 'resizable=no,status=no,location=no,toolbar=no,menubar=no,fullscreen=no,scrollbars=no,dependent=no'); return false;"&gt;五天学习计划&lt;/a&gt;。&lt;/li&gt;&#13;
	&lt;/ul&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;在开始你的项目之前先进行锻炼或者冥想&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;h3&gt;&lt;em&gt;&lt;strong&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/strong&gt;&lt;/em&gt;拖延,是因为我感到沮丧&lt;/h3&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;规划好你的休息时间在你的工作当中。&lt;/li&gt;&#13;
	&lt;li&gt;学习新知识必然会有些许挑战,沮丧必然会成为其中的一部分,请牢记这一点。&lt;/li&gt;&#13;
	&lt;li&gt;培养成长心态可以有所帮助!&lt;/li&gt;&#13;
	&lt;li&gt;当你遇到有挑战的知识时可以寻求他人的帮助。&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;h3&gt;&lt;strong&gt;&lt;em&gt;&amp;nbsp; &amp;nbsp; &lt;/em&gt;&lt;/strong&gt;拖延,是因为我的完美主义&lt;/h3&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;完美主义者经常觉得他们需要坐下来思考自己的想法,直到项目完美地出现。但是这不是通常情况下的工作方式,时刻提醒或者说服自己做草稿,草稿是很强大的工具。如果你做了一个草稿并思考了一段时间,大多数想法/项目/论文都会得到改进。草稿给你时间和空间,让你的想法有效地炖煮。&lt;/li&gt;&#13;
	&lt;li&gt;首先设定一个目标,不是完成一篇论文,而是开始起草草稿。&lt;/li&gt;&#13;
	&lt;li&gt;项目最终的结果真的需要完美无缺吗?尝试着让自己觉得足够好就可以了。但是如果一味的追求平庸那也不是一个好的想法,但是如果策略性的追求&amp;ldquo;足够好&amp;rdquo;那是没问题的。&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;h3&gt;&lt;strong&gt;&lt;em&gt;&amp;nbsp; &amp;nbsp; &lt;/em&gt;&lt;/strong&gt;拖延,是因为我的懒惰&lt;/h3&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;大部分人发现结束一项工作比开始一项工作要难。因此不要把完成某个项目当做你最终的目标,你只要启动它就行了。&lt;/li&gt;&#13;
	&lt;li&gt;如何开始呢?制定一个5分钟的计时,关闭所有让你分心的东西,在这5分钟内只做与你项目相关的事情,时间结束之后,如果你想继续做那就继续做下去。不管怎么说,现在你已经开始了。&lt;/li&gt;&#13;
	&lt;li&gt;你是否想等到恐慌来侵扰你内心的时候才打算开始吗?如果你不想为了完成事情而感到恐慌,那就通过管理你的时间和日历来避免恐慌。&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;h3&gt;&lt;strong&gt;&lt;em&gt;&amp;nbsp; &amp;nbsp; &lt;/em&gt;&lt;/strong&gt;拖延,是因为我的执行力不足&lt;/h3&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;弄清楚你需要完成的事情与你目标之前的潜在关系,这样就可以获得足够的动力去执行。&lt;/li&gt;&#13;
	&lt;li&gt;注意你的自言自语:尝试用 &amp;quot;我想 &amp;quot;代替 &amp;quot;我必须&amp;quot;。&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;h3&gt;&lt;em&gt;&lt;strong&gt;&amp;nbsp; &amp;nbsp; &lt;/strong&gt;&lt;/em&gt;拖延,是因为自我怀疑&lt;/h3&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;花些许的时间消除消极的想法,诚实地盘点你的成就。恭喜你,努力工作成就了现在的你,请继续保持努力!&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;h3&gt;&lt;strong&gt;&lt;em&gt;&amp;nbsp; &amp;nbsp; &lt;/em&gt;&lt;/strong&gt;拖延,是因为愤怒&lt;/h3&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;如果你不做作业是因为你对老师很生气或者你不喜欢这门课,扪心自问:你不做作业伤害了谁?&#13;
	&lt;ul&gt;&#13;
		&lt;li&gt;&#13;
		&lt;p&gt;怎样才能有效地发泄你的愤怒呢?&lt;/p&gt;&#13;
		&lt;/li&gt;&#13;
	&lt;/ul&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;h3&gt;&lt;strong&gt;&lt;em&gt;&amp;nbsp; &amp;nbsp; &lt;/em&gt;&lt;/strong&gt;拖延,是因为我真的没有时间&lt;/h3&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;考虑下自己为何如此之忙?你可以与自己或与他人重新协商哪些承诺?&lt;/li&gt;&#13;
	&lt;li&gt;更好地计划时间,并允许自己停止过度安排。&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;循环的拖延症怪圈&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;&lt;img alt="" class="d-block img-fluid mx-auto pic" src="https://7.dusays.com/2021/04/01/05d91002dc616.png" /&gt;&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;原文链接:&lt;a href="https://lsc.cornell.edu/break-the-cycle-of-procrastination/"&gt;Break the Cycle of Procrastination&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;翻译如果不当之处,请评论处留言,互相学习,拒绝拖延!!!&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/39/" rel="alternate"/>
  </entry>
  <entry>
    <id>38</id>
    <title>[测试相关]Selenium自动化测试工具简单使用示例</title>
    <updated>2023-09-27T02:08:19.929421+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="1.安装浏览器驱动" name="1.安装浏览器驱动"&gt;&lt;/a&gt;1.安装浏览器驱动&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;访问&lt;a href="https://chromedriver.storage.googleapis.com/index.html?path=&amp;amp;sort=desc" onclick="window.open(this.href, 'chrome', 'resizable=yes,status=no,location=yes,toolbar=no,menubar=yes,fullscreen=no,scrollbars=yes,dependent=no'); return false;"&gt;下载链接&lt;/a&gt;,既可以进入&lt;code&gt;chrome&lt;/code&gt;驱动下载页面,目前&lt;code&gt;selenium&lt;/code&gt;支持chrome、Firefox、IE 以及Remote。&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;Windows安装&lt;/p&gt;&#13;
&#13;
	&lt;p&gt;将下载好的驱动文件放入任意位置即可,比如放在&lt;code&gt;F:/ChormeDriver/chormedriver.exe&lt;/code&gt;中,我们将其路径设置到环境变量的&lt;code&gt;Path&lt;/code&gt;中即可;&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;Linux中安装&lt;/p&gt;&#13;
&#13;
	&lt;p&gt;将下载好的驱动文件放入&lt;code&gt;/usr/bin&lt;/code&gt;目录中即可&lt;/p&gt;&#13;
&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo cp chromedriver /usr/bin&lt;/code&gt;&lt;/pre&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="2.安装selenium" name="2.安装selenium"&gt;&lt;/a&gt;2.安装selenium&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在你项目的环境中通过下面的命令进行安装&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;pip install selenium -i https://pypi.douban.com/simple&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上面的&lt;code&gt;-i&lt;/code&gt;参数是可选的,是指定在哪个&lt;code&gt;pip&lt;/code&gt;在哪个源搜索&lt;code&gt;selenium&lt;/code&gt;,这里指定的是douban的源,下载速度相较于默认的&lt;code&gt;python.org&lt;/code&gt;要更加的快。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="3.简单的示例" name="3.简单的示例"&gt;&lt;/a&gt;3.简单的示例&lt;/h1&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from selenium import webdriver&#13;
&#13;
driver = webdriver.Chrome()&#13;
driver.get('https://www.baidu.com')&#13;
print(driver.title)&#13;
assert '百度' in driver.title&#13;
assert '我是' in driver.title&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在上面的代码中,通过访问&lt;code&gt;https://www.baidu.com&lt;/code&gt;来获取该网站的title信息,并判断&lt;code&gt;百度&lt;/code&gt;以及&lt;code&gt;我是&lt;/code&gt;是否在title中,运行结果如下。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://7.dusays.com/2021/03/23/97731ba7128ea.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;因为&lt;code&gt;我是&lt;/code&gt;没有包含在&lt;code&gt;title&lt;/code&gt;中所以出错。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="4.模拟键盘输入" name="4.模拟键盘输入"&gt;&lt;/a&gt;4.模拟键盘输入&lt;/h1&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from selenium import webdriver&#13;
from selenium.webdriver.common.keys import Keys&#13;
&#13;
driver = webdriver.Chrome()&#13;
driver.maximize_window()&#13;
driver.get("https://2dogz.cn")&#13;
assert "Blogin" in driver.title&#13;
elem = driver.find_element_by_name("q")&#13;
elem.clear()&#13;
elem.send_keys("装饰器")&#13;
elem.send_keys(Keys.RETURN)&#13;
assert "装饰器" in driver.page_source&#13;
assert "No results found." in driver.page_source&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;通过&lt;code&gt;Keys&lt;/code&gt;模块我们可以模拟键盘的输入,在上面的代码中我们做了如下的事情&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;通过&lt;code&gt;find_element_by_name&lt;/code&gt;来获取搜索的输入框&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;通过&lt;code&gt;send_keys&lt;/code&gt;在输入框中嵌入搜索关键字&lt;code&gt;百度一下&lt;/code&gt;&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;通过&lt;code&gt;send_keys&lt;/code&gt;模拟按下&lt;code&gt;ENTER&lt;/code&gt;按键,进入下一个搜索结果页面&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;在搜素结果页面中判断&lt;code&gt;装饰器&lt;/code&gt;是否在其中&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;在搜索页面中判断&lt;code&gt;No results found&lt;/code&gt;是否在其中&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;执行结果如下&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;浏览器页面&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://7.dusays.com/2021/03/23/47967cad6b570.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;控制台输出&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://7.dusays.com/2021/03/23/83eb4eb4088ae.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="5.结合unittest使用" name="5.结合unittest使用"&gt;&lt;/a&gt;5.结合unittest使用&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;&lt;code&gt;selenium&lt;/code&gt;自身不提供测试框架或者工具,我们可以通过Python 的&lt;code&gt;unittest&lt;/code&gt;模块来写测试用例,也可以通过其他的的第三方测试工具如&lt;code&gt;pytest&lt;/code&gt;或者&lt;code&gt;nose&lt;/code&gt;等。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;import unittest&#13;
from selenium import webdriver&#13;
from selenium.webdriver.common.keys import Keys&#13;
&#13;
&#13;
class BaiduTest(unittest.TestCase):&#13;
    def setUp(self) -&amp;gt; None:&#13;
        self.driver = webdriver.Chrome()&#13;
&#13;
    def test_title(self):&#13;
        driver = self.driver&#13;
        driver.get('https://2dogz.cn')&#13;
        self.assertIn('Blogin', driver.title)&#13;
&#13;
&#13;
    def test_blog_search(self):&#13;
        driver = self.driver&#13;
        driver.get('https://2dogz.cn')&#13;
        search = driver.find_element_by_name('q')&#13;
        search.send_keys('装饰器')&#13;
        search.send_keys(Keys.RETURN)&#13;
        self.assertIn('装饰器', driver.page_source)&#13;
        self.assertNotIn('没有找到', driver.page_source)&#13;
        &#13;
&#13;
    def tearDown(self) -&amp;gt; None:&#13;
        self.driver.close()&#13;
&#13;
&#13;
if __name__ == '__main__':&#13;
    unittest.main()&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://7.dusays.com/2021/03/23/4025fa784de4e.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;通过&lt;code&gt;unittest&lt;/code&gt;模块将其与&lt;code&gt;selenium&lt;/code&gt;结合起来实现自动化测试,上面的结果说明两个测试都是通过的,我们从网页中手动去搜索发下也是一样的结果。&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/38/" rel="alternate"/>
  </entry>
  <entry>
    <id>37</id>
    <title>[应用部署]使用COS备份云服务器数据</title>
    <updated>2023-09-27T02:08:19.929394+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="1. 什么是对象存储" name="1. 什么是对象存储"&gt;&lt;/a&gt;1. 什么是对象存储&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;COS(Cloud Object Storage,COS)的意思就是对象存储,提供的一种存储海量文件的分布式存储服务,用户可通过网络随时存储和查看数据。在COS中有两个概念一个是bucket还有一个是object。在这里bucket就相当于我们电脑上的硬盘,用来存储object的,从而object就相当于是我们电脑硬盘中每一个文件。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="2. 创建BUCKET" name="2. 创建BUCKET"&gt;&lt;/a&gt;2. 创建BUCKET&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;首先登录腾讯云的控制台页面,在所有产品中输入COS,就可以找到对象存储的页面,按照页面上的提示一步一步的操作即可,最后会进入到如下页面&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="创建bucket" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/1347image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;这里要注意的是,最好是创建一个跟自己云服务在同一个区域的对象存储,因为在同一个区域的话,就不会耗费对象上传所需要的的费用了。当然如果你是土豪,那么可以忽略这一点。&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="3. 查看网络是否连通" name="3. 查看网络是否连通"&gt;&lt;/a&gt;3. 查看网络是否连通&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;登录到自己的云服务器,通过telnet命令来判断云服务器晕COS之间的网络是否连通,命令如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;telnet examplebucket-1250000000.cos.ap-guangzhou.myqcloud.com 80&#13;
Trying 169.254.0.47....&#13;
Connected to 169.254.0.47.&#13;
Escape character is '^]'.&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;连接可以到自己的COS对应的bucket概览中查看,如果出现上述结果,则说明云服务器与COS是连通的。.&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="4.上传文件到bucket中" name="4.上传文件到bucket中"&gt;&lt;/a&gt;4.上传文件到bucket中&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;上传文件到bucket中的方式有很多种,这里只介绍通过Python SDK的方式上传文件到bucket。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;首先需要先通过pip安装第三方库,命令如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;pip install -U cos-python-sdk-v5&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;然后需要获取两个关键认证参数,SecretId与SecretKey,访问&lt;a href="https://console.cloud.tencent.com/cam/capi"&gt;该页面&lt;/a&gt;既可以获取到&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/8397image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;然后新建一个脚本文件输入如下代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;# -*- coding=utf-8&#13;
# appid 已在配置中移除,请在参数 Bucket 中带上 appid。Bucket 由 BucketName-APPID 组成&#13;
# 1. 设置用户配置, 包括 secretId,secretKey 以及 Region&#13;
from qcloud_cos import CosConfig&#13;
from qcloud_cos import CosS3Client&#13;
import sys&#13;
import logging&#13;
logging.basicConfig(level=logging.INFO, stream=sys.stdout)&#13;
secret_id = 'COS_SECRETID'      # 替换为用户的 secretId&#13;
secret_key = 'COS_SECRETKEY'      # 替换为用户的 secretKey&#13;
region = 'COS_REGION'     # 替换为用户的 Region&#13;
token = None                # 使用临时密钥需要传入 Token,默认为空,可不填&#13;
scheme = 'https'            # 指定使用 http/https 协议来访问 COS,默认为 https,可不填&#13;
config = CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key, Token=token, Scheme=scheme)&#13;
# 2. 获取客户端对象&#13;
client = CosS3Client(config)&#13;
with open('picture.jpg', 'rb') as fp:&#13;
   response = client.put_object(&#13;
       Bucket='examplebucket-1250000000',&#13;
       Body=fp,&#13;
       Key='picture.jpg',&#13;
       StorageClass='STANDARD',&#13;
       EnableMD5=False&#13;
   )&#13;
print(response['ETag'])&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上述代码中的参数根据自己实际的bucket参数进行配置,运行上面的脚本就可以将数据上传到bucket中了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;如果我们希望在bucket中对文件进行分类,可以在腾讯云控制台中新建文件夹,如下图&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/5064image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;如果我们希望将picture.jog上传到博客图片文件中,修改上面的代码为如下所示就可以了&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;with open('picture.jpg', 'rb') as fp:&#13;
   response = client.put_object(&#13;
       Bucket='examplebucket-1250000000',&#13;
       Body=fp,&#13;
       Key='博客图片/picture.jpg',&#13;
       StorageClass='STANDARD',&#13;
       EnableMD5=False&#13;
   )&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;之后在控制台就可以手动下载你想下载的文件啦,速度非常快,但是还是需要注意费用哦,如果文件体量不大,一年也花不了太多钱。&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/37/" rel="alternate"/>
  </entry>
  <entry>
    <id>36</id>
    <title>[Python]给个人博客添加RSS订阅支持</title>
    <updated>2023-09-27T02:08:19.929368+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="1.RSS指北" name="1.RSS指北"&gt;&lt;/a&gt;1.RSS指北&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;可能很多小伙伴看到RSS这个缩写或多或少都不太明白其意思,这里稍微做一下指北介绍&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;a.什么是RSS&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;RSS&lt;/strong&gt;是单词&lt;strong&gt;Really Simple Syndication&lt;/strong&gt;三个单词的首个字母,其意思就是简易信息聚合(翻译来自百度百科),它基于XML标准,是一种在互联网上被广泛采用的内容包装和投递协议。RSS是一种描述和同步网站内容的格式,是使用最广泛的XML应用。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;b.为什么要采用RSS&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;假设我有一个个人的博客网站,如果某些用户对我的网站比较感兴趣,一般的操作方式就是收藏我的网站,然后不定期的访问网站来查看我的网站是否有内容进行更新,这样一来对于用户来说就比较耗费时间。如果我的网站提供了RSS订阅源,那么用户只需要订阅我网站的RSS源,就不需要不定期的来访问我的网站来查看是否有内容更新了。当用户订阅了我网站的RSS源之后,每当我的网站有内容更新,用户端的RSS源也会随之更新,这就是RSS的好处,既可以方便用户又可以有效的传播我们的文章。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="2.Python生成RSS源" name="2.Python生成RSS源"&gt;&lt;/a&gt;2.Python生成RSS源&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;我的网站是基于Python3+Flask来搭建的,所以这里就介绍一下如果使用Python来生成RSS源。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;其实&lt;code&gt;RSS&lt;/code&gt;本质就是一个&lt;code&gt;.xml&lt;/code&gt;文件,我们可以通过很多中方法来生成&lt;code&gt;.xml&lt;/code&gt;文件,在这里我使用&lt;code&gt;python-feengen&lt;/code&gt;这个第三方库来实现RSS源的生成。首先通过下面的命令安装&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;pip install feedgen&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;安装完成之后,根据官方文档写一个简单的示例,代码清单如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from feedgen.feed import FeedGenerator&#13;
&#13;
fg = FeedGenerator()&#13;
&#13;
fg.id('https://2dogz.cn/')&#13;
fg.title('Blogin')&#13;
fg.author({'name': 'Blogin', 'email': 'weijiang1994_1@qq.com'})&#13;
fg.link(href='https://2dogz.cn/')&#13;
fg.logo('https://2dogz.cn/accounts/avatar/%E6%B8%85%E6%B0%B44.jpg/')&#13;
fg.subtitle('这是一个测试feed')&#13;
# 生成feed&#13;
print(fg.atom_str(pretty=True))  # 这里输出的是bytes类型的数据&#13;
print(str(fg.atom_str(pretty=True), encoding='utf-8'))&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;运行结果如下图所示,我们可以看到就是一个xml组织结构的文本&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="feed" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn//backend/files/3164image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;如果你想将其写入&lt;code&gt;xml&lt;/code&gt;文件中可以通过&lt;code&gt;fg.atom_file(&amp;#39;filename.xml&amp;#39;)&lt;/code&gt;将其存入到xml文件中去!&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;上面仅仅是生成feed,我们还需要将实例(entry)填入其中,在上面的代码中加入下面的代码清单&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;fe = fg.add_entry()&#13;
fe.id('https://2dogz.cn/blog/article/34/')&#13;
fe.title('不期而至的三月雨')&#13;
fe.content('白日依山尽,黄河入海流,欲穷千里目,更上一层楼。')&#13;
print(str(fg.atom_str(pretty=True), encoding='utf-8'))&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;结果如下图,我们可以看到新添加进去的内容被放入到&lt;strong&gt;feed&lt;/strong&gt;节点中了已&lt;strong&gt;entry&lt;/strong&gt;作为节点名&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="http://2dogz.cn/backend/files/8278image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;这样就完成了RSS源的生成了。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="3.在Flask中使用" name="3.在Flask中使用"&gt;&lt;/a&gt;3.在Flask中使用&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;最终的目的还是要给我们自己的博客网站提供一个RSS的源,供用户订阅,那么怎么来实现呢?看下面代码清单&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@rss_bp.route('/')&#13;
def rss_feed():&#13;
    blogs = Blog.query.filter_by(delete_flag=1).order_by(Blog.create_time.desc()).all()&#13;
    fg = FeedGenerator()&#13;
    fg.title('Blogin')&#13;
    fg.description('Blogin是一个个人博客网站,后端使用Flask框架,前端使用Bootstrap4,主要分享一些编程类的技术以及一些陈词滥调的文章!')&#13;
    fg.link(href='https://2dogz.cn')&#13;
    fg.id(str(len(blogs)))&#13;
    for blog in blogs:&#13;
        fe = fg.add_entry()&#13;
        fe.title('[{}]'.format(blog.blog_types.name)+blog.title)&#13;
        fe.id(blog.id)&#13;
        fe.link(href='https://2dogz.cn//blog/article/{}/'.format(blog.id))&#13;
        fe.description(blog.introduce)&#13;
        fe.guid(str(blog.id), permalink=False)  # Or: fe.guid(article.url, permalink=True)&#13;
        fe.author(name='Blogin', email='weijiang1994_1@qq.com')&#13;
        fe.content(blog.content)&#13;
&#13;
    rss_data = str(fg.atom_str(pretty=True), 'utf-8')&#13;
    response = make_response(rss_data)&#13;
    response.headers.set('Content-Type', 'rss+xml')&#13;
    return response&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上面的代码来自我自己博客网站rss源生成的代码,其具体操作流程就是先将博客网站上所有的文章从数据库中提取出来,然后通过feedgen来生成.xml标准的文本,然后通过response将其作为返回。我们访问&lt;a href="https://2dogz.cn/rss-feed/"&gt;https://2dogz.cn/rss-feed/&lt;/a&gt;就可以看到效果了。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="4.需要注意的地方" name="4.需要注意的地方"&gt;&lt;/a&gt;4.需要注意的地方&lt;/h1&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;在给response设置headers的时候记得需要将其设置为rss+xml,不需要设置为application/rss+xml,否则会出现乱码&lt;/li&gt;&#13;
	&lt;li&gt;博客中如果使用到了图片需要将其设置为绝对路径,否则在RSS阅读器中可能无法看到博客中的图片&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/36/" rel="alternate"/>
  </entry>
  <entry>
    <id>35</id>
    <title>[MySQL]MySQL查询之排序分页</title>
    <updated>2023-09-27T02:08:19.929340+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="1.排序的基本用法" name="1.排序的基本用法"&gt;&lt;/a&gt;1.排序order by的基本用法&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在MySQL中排序的基本语法如下所示&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;# 单字段&#13;
SELECT column1, column2 from table order by column1 desc;&#13;
&#13;
# 多字段&#13;
SELECT column1, column2 from table order by column1, column2 desc;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;通过下面的语句新建一张student表,并插入一些数据&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;CREATE TABLE student(&#13;
    id int primary key auto_increment,&#13;
    name varchar(128) default '' not null ,&#13;
    score integer default 0 not null ,&#13;
    birth date not null &#13;
)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;插入一些测试数据&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;INSERT INTO student (name, score, birth) VALUES&#13;
('张三', 89, '2005-08-12'),&#13;
('李四', 78, '2002-11-03'),&#13;
('王五', 89, '2004-09-11'),&#13;
('赵六', 68, '2005-08-24'),&#13;
('赵七', 59, '2001-04-22'),&#13;
('钱八', 95, '2008-08-31');&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h2&gt;a.单字段排序&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;通过&lt;code&gt;分数&lt;/code&gt;对查询结果进行排序,升序排列&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;select name, score, birth from student order by score;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;查询结果&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="分数单字段" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/8373%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_69.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;通过&lt;code&gt;分数&lt;/code&gt;对查询结果进行排序,降序排列&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;select name, score, birth from student order by score desc ;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;查询结果&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;&lt;img alt="分数降序" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/2697%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_70.png" /&gt;&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;b. 多字段排序&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;上面的数据中我们可以看到分数有相同的情况,如果某个字段存在相同的数据,那么可以用其他字段作为第二个、第三个等次要条件来排序&lt;/p&gt;&#13;
&#13;
&lt;p&gt;使用&lt;code&gt;score&lt;/code&gt;、&lt;code&gt;birth&lt;/code&gt;字段来排序&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;select name, score, birth from student order by score desc , birth desc;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;&amp;nbsp;查询结果&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="多字段排序" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/1513%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_71.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;多字段我们可以指定每个字段的排序方式(升序或者降序)!&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;h2&gt;&amp;nbsp;c.按函数排序&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;除了字段之外,我们还可以使用内置的函数来排序,比如通过&lt;code&gt;birth&lt;/code&gt;字段的年份来排序&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;select name, score, year(birth), birth from student order by year(birth);&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;查询结果&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/4846%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_72.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="2.分页limit的用法" name="2.分页limit的用法"&gt;&lt;/a&gt;2.分页limit的用法&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;limit顾名思义,是用来显示查询返回的长度,其语法如下所示&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;SELECT column1, column2 from table limit offset, count;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;offset:表示偏移量,就是跳过多少行数据,可以省略默认为0&lt;/li&gt;&#13;
	&lt;li&gt;count:表示去多少行数据,如果设置了offset则跳过offset行数据后开始取数据&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;h2&gt;a.获取前n行记录&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;我们想获取成绩排名前面的学生&lt;/p&gt;&#13;
&#13;
&lt;p&gt;首先我们需要对数据进行降序排列,然后通过limit来获取前3排名的数据&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;select name, score from student order by score desc limit 3;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;查询结果&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="limit前三" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/4898%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_73.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;b.获取n-m行记录&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;如果想获取成绩排名2-4名的学生,通过limit怎么实现呢?&lt;/p&gt;&#13;
&#13;
&lt;p&gt;首先我们要跳过第一名就是将offset设置为1,同时要取2-4名count设为3;&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;offset: n-1&lt;/li&gt;&#13;
	&lt;li&gt;count: m-n+1&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;select name, score from student order by score desc limit 1, 3;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;查询结果&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;&lt;img alt="" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/8649%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_74.png" /&gt;&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="3.分页查询" name="3.分页查询"&gt;&lt;/a&gt;3.分页查询&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在实际开发过程中我们经常会遇到分页查询的功能,一般的都通过limit来实现,当然还有一些邪道,这里就不说。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;上面插入的数据量太小了,我们需要重新插入一下测试数据&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;insert into student (name, score, birth) values&#13;
('李明', FLOOR(RAND()*100), '2006-06-06'),&#13;
('李红', FLOOR(RAND()*100), '2001-01-23'),&#13;
('王宝静', FLOOR(RAND()*100), '2006-04-18'),&#13;
('李耀武', FLOOR(RAND()*100), '2003-11-11'),&#13;
('宋青书', FLOOR(RAND()*100), '2006-12-08'),&#13;
('任我行', FLOOR(RAND()*100), '2006-08-22'),&#13;
('裘千仞', FLOOR(RAND()*100), '2006-01-24'),&#13;
('小龙女', FLOOR(RAND()*100), '2006-07-26')&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上面使用了生成随机数的函数,大家可以自行百度一下。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;执行select语句,看看数据是否插入成功&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/5906image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;比如我们按照成绩降序,需要查询第2页,每页5条数据,怎么实现呢?&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;第1页 5条数据 1-5,offset=0 count=5&lt;/li&gt;&#13;
	&lt;li&gt;第2页 5条数据 6-10 offset=5 count=5&lt;/li&gt;&#13;
	&lt;li&gt;第3页 5条数据 11-15 offse=10 count =5&lt;/li&gt;&#13;
	&lt;li&gt;...&lt;/li&gt;&#13;
	&lt;li&gt;第n页 5条数据 (n-1)*5 - 5n offset=(n-1)*5 count=5&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;select name, score, birth from student order by score desc limit 5, 5;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;查询结果&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/7172image.png" /&gt;&amp;nbsp;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="4.避免踩坑" name="4.避免踩坑"&gt;&lt;/a&gt;4.避免踩坑&lt;/h1&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;limit后面不能跟表达式&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;# 报错,limit后面不能跟表达式&#13;
select * from student where limit 1,4+1;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
	&lt;p&gt;这种写法是会报错,limit后面只能跟具体的数字&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;limit后面不能为负数&lt;/p&gt;&#13;
&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;# 报错,limit后面不能跟负数&#13;
select * from student where limit -1,-4;&lt;/code&gt;&lt;/pre&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;排序分页问题&lt;br /&gt;&#13;
	排序最好用两个字段进行排序,如果一个字段中可能出现同等的值,那么就会造成排序分页冗余的问题,可以指定多个字段进行排序,保证其排序结果的唯一性!&lt;br /&gt;&#13;
	&amp;nbsp;&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/35/" rel="alternate"/>
  </entry>
  <entry>
    <id>34</id>
    <title>[陈词滥调]不期而至的三月细雨</title>
    <updated>2023-09-27T02:08:19.929313+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;p&gt;&amp;nbsp; &amp;nbsp; 自从15年9月去桂林读研之后,已经许久没有见过潇湘阳春三月的样子了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp; &amp;nbsp; 在读研的三年时间里,每年中间还能间断的回来几次,不过那时候的故乡也只有夏冬。印象中读研期间的第一个寒假回家,回家那天晚上下了大学,还兴致勃勃的在秋秋空间中发布了一条打油诗的说说,现在看起来当初自己的感觉好蠢啊,如果放到现在我肯定不会厚着脸皮往朋友圈发。&lt;br /&gt;&#13;
&lt;img alt="归雪" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/8305%E5%BD%92%E9%9B%AA.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp; &amp;nbsp; 思绪回转到桂林,桂林的天气真的就是一个女人,十分的善变。很多次,跟着连同学骑着小电炉去市区溜达,出门的时候还是晴空大太阳,突然之间来了一团乌云,然后暴雨将至,因此出门的时候我们总是不约而同的在小电炉的后备箱中放一把小雨伞,以防万一。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp; &amp;nbsp; 岭南地区流传着&amp;rdquo;回南天&amp;ldquo;的说法,记得研一下学期开学时,那天晚上刚回到学校,就感觉自己床铺上的被子十分的潮湿,简直不能入睡,而且我还是在上铺,可想而知睡在我下铺的兄弟会怎么样。那时候研一住在一楼,三四月份的时候,宿舍的地板天天都是湿漉漉的,晾在外面的衣服也总是发霉。研一结束之后宿舍楼上就有了空宿舍,几个舍友就一起搬到3楼去了。搬到了3楼之后,回南天带来的影响就相对1楼很小了,至少宿舍地板不是每天都很潮湿了,这大概就是我对桂林这个城市最深刻的记忆了,哦对了,还有那便宜有很好吃的荔枝了,哈哈哈。总而言之,桂林给我的第一感觉就是潮湿还有那种山雨欲来风满楼的感觉~&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp; &amp;nbsp; 18年6月底毕业之后离开了这个生活、学习了三年的地方了,记得最后一次离开桂林是同张同学一起坐车离开的,在开往桂林站的的士上,路过解放桥时拍下了在桂林的最后一张照片,关于桂林的记忆就停留于此了,但是关于桂林的人和事有很多很多!&lt;br /&gt;&#13;
&lt;img alt="桂林" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/2557%E6%A1%82%E6%9E%97.jpg" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp; &amp;nbsp; 毕业之后来到了深圳,这是第二次来到深圳了。早在17年就有过一面之缘,那时候由于身体抱恙,来深圳看了一下(各位还是要多注意一下自己的身体啊~不要太劳累)。大城市真的有大城市的便利,不管是交通还是其他方面,但是便利归便利,这里的灯红酒绿终究是不属于我。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp; &amp;nbsp; 第一年来到深圳恰逢18年的山竹大台风,那一天恰巧是星期天,那算是生平的初见台风。那时候住在公司给应届生分配的宿舍中,好在有很多小伙伴一起聊天、扯淡,来度过了内心比较恐惧的一天,那天还停电了很久直到第二天晚上下班回到宿舍才来电,9月份的深圳十分炎热,不敢去想那晚我是怎么入睡的。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp; &amp;nbsp; 深圳的雨相比桂林就温和多了,至少我没有遇到过突然之间就下来磅礴大雨,但是很烦人的是深圳的雨总是喜欢在上下班的时候下,可能是在提醒奔波的人们放慢脚步当心一点。18年底公司给应届生住的宿舍到期了,迫不得已得自己到外面去租房住了,就跟同事小张总一起在西乡共乐小学附近合租了一间两室一厅城中村的房子。由于出租屋附近没有地铁,每天只能挤着公交车上下班,生平第一次坐着人挤人的公交车,有一次感觉整个身子都是飘起来的,脚根本没有着地,很多时候就连公交车师傅开门的时候都会吆喝一声&amp;quot;站在门口的注意啦,要开门了~&amp;quot;,不幸中的万幸是我们那站离发车站不算太远每次都能挤上车,而且不会被挤到门口附近。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp; &amp;nbsp; 就这样日子一天天无聊的过去了,每天除了上班下班睡觉就没有其他的事情可以做了,住在这边的这段时间里最烦人就是每天打不死、灭不尽的小强了。时间快进到19年6月底,这边的房子要到期了,跟小张总合计着租到有地铁的地方去,这样子上班就相对比较方便了。在离租期结束的上一个周末就去固戍那边找房子,接纳我们的中介比较好,是个漂亮的小妹子,骑着小电驴在地铁口等着我们,后来跟着她去了要出租的公寓,这期间还往机场东那边跑了一圈,可惜距离太远而且价格也没有便宜太多,后来就还是租在固戍这边了。去机场东的途中是我第一次在深圳骑电动车,还跟着中介一起闯红灯了(主要是没车o)。这边就没有跟小张总一起合租了,因为这时候我有女朋友了,所以就跟他租的同一栋楼同一层的单间了。不得不说深圳的黑房东是真的很多,第一次退房黑房东就扣了我们900块钱,一个灯要300块。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp; &amp;nbsp; 坐地铁上班的日子也没有想象中的那么美好,每天早上7点就要起床,然后30之前必须出门,不然固戍地铁口就是一条长龙了。由于固戍离市中心的距离比较远了,这边的房租相对比较便宜,很多在南山、福田上班的人都住在这边,所以每天早上就能看到深圳奇观,地铁口一条条长龙,不管是几号线,都是这样。排队只是开始,在进入地铁之后,你可能还得等待第二趟地铁才能上车,好在是深圳地铁的频率比较高,不用等待太久。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp; &amp;nbsp; 2020年初疫情来了,不得不说985毕业的小张总还是很有远见的,在关于疫情新闻才报道出来的时候,他就拉着我去买口罩,而我那时候却对他这种想法有点不屑,但是还是跟他一起去买了。2020年春节由于疫情公司延迟了一周的假期,回到深圳之后,昔日里繁忙的一号线看不到几个踪影。后来根据社区通知,我是从岳阳返深的需要居家隔离14天,两周真的是一天没出过门,买菜都是外送。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp; &amp;nbsp; 时间来到年中,6月份恰好找到了长沙的工作,然后就跟公司提交了辞呈,好在我经受住了领导画的大饼,最后在7月初就离职了,离开了第一个老东家。后面大半个月的时间过得很悠闲,去珠海玩了一趟,然后回深圳收拾完东西之后退完房,就回岳阳老家了。在老家待了不到一个星期就回到了长沙入职了,到现在也有大半年的时间了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp; &amp;nbsp; 三月份的长沙,许久未见的朦胧细雨又如期而至,仿佛又回到大学那时。不知道是什么原因,好像每个地方的雨都喜欢在上下班的时候下,不管是深圳还是在长沙好像都是这样。长沙还是正在发展中的城市,在公共交通肯定是没有深圳那么便利,公司所在的地方属于是商业区,但是现在还没有开通地铁,只有公交车,而且下了公交车步行的距离也比较远。并且已经在深圳享受了一大早坐公交车那种晕晕吐吐的感觉,在长沙就不想再次体验了。因此每天就骑着共享电动车上下班了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp; &amp;nbsp; 连续多天的绵绵细雨,扰乱我的心神。很多次闹钟里面播放着今天的天气是多云,而等我出门就又在下雨了。而后就只能走路去公司,虽然不远只有3公里左右的路程,40分钟就能到,但是出门晚就迟到了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp; &amp;nbsp; 在外飘摇了五年多的时间,最后还是回到了热爱的潇湘,虽然说比不上大城市的繁华,但是还是比较喜欢这里的市井气息,让人的生活更加惬意~&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp; &amp;nbsp; 不期而至的三月雨啊,你不要再上下班的时候下啦,不然打工人的全勤奖就没啦~&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/34/" rel="alternate"/>
  </entry>
  <entry>
    <id>33</id>
    <title>[系列教程]使用Flask搭建一个校园论坛7-帖子详情</title>
    <updated>2023-09-27T02:08:19.929285+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="1.功能简介" name="1.功能简介"&gt;&lt;/a&gt;1.功能简介&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;不管什么论坛,当用户发布了帖子之后,最原始的目的就是让更多的用户来看到帖子,同时参与到该帖子的讨论当中去。到目前为止我们还没有实现用户阅读帖子的页面,本节主要实现这个功能。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="2.阅读帖子" name="2.阅读帖子"&gt;&lt;/a&gt;2.阅读帖子&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在首页中我们显示了三个tab的帖子列表,用户点击对应的帖子标题超链接就进入对应的详情页面,但是在上一节之前还没有实现阅读帖子的视图函数,所以上一节中的点击帖子标题跳转到帖子详情页面的功能还不能使用,这里开始实现此功能。打开&lt;code&gt;bbs/blueprint/frontend/post.py&lt;/code&gt; 模块,添加下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@post_bp.route('/read/&amp;lt;post_id&amp;gt;/', methods=['GET'])&#13;
def read(post_id):&#13;
    post = Post.query.get_or_404(post_id)&#13;
    if current_user.is_authenticated:&#13;
        c_tag = Collect.query.filter(Collect.user_id == current_user.id, Collect.post_id == post_id).first()&#13;
    else:&#13;
        c_tag = None&#13;
    post.read_times += 1&#13;
    db.session.commit()&#13;
    return render_template('frontend/post/read-post.html', post=post, c_tag=c_tag)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上面代码十分简单,就是通过请求中的参数从数据库中获取帖子的相关内容,然后渲染到页面中,但此时还没有对应的页面,因此需要创建模板文件,打开&lt;code&gt;bbs/templates/frontend/post/&lt;/code&gt;,新建模板文件&lt;code&gt;read-post.html&lt;/code&gt;,并添加下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;{% extends "frontend/base.html" %}&#13;
{% block head %}&#13;
    {{ super() }}&#13;
    {{ ckeditor.load_code_theme() }}&#13;
    &amp;lt;!--suppress ALL --&amp;gt;&#13;
    &amp;lt;style&amp;gt;&#13;
        .post-title-h1{&#13;
            font-size: 22px;&#13;
            font-weight: bold;&#13;
        }&#13;
&#13;
        .post-div{&#13;
            color: white;&#13;
            background: #303030;&#13;
            padding: 5px 15px 8px 15px;&#13;
            border-radius: 5px;&#13;
        }&#13;
&#13;
        .post-content{&#13;
            color: white;&#13;
            background: #343434;&#13;
            padding: 8px;&#13;
            border-radius: 5px;&#13;
        }&#13;
&#13;
        article&amp;gt;h1 {&#13;
            font-size: 20px;&#13;
            font-weight: bold;&#13;
            margin: 10px 0 10px 0;&#13;
            padding: 0 10px;&#13;
            border-left: 5px solid #20c997;&#13;
            line-height: 2em;&#13;
        }&#13;
&#13;
        article&amp;gt;h2 {&#13;
            font-size: 18px;&#13;
            margin-top: 5px;&#13;
            margin-bottom: 5px;&#13;
            padding: 5px 5px 5px 5px;&#13;
            border-bottom: 1px solid #28a745;&#13;
        }&#13;
&#13;
        article&amp;gt;h3 {&#13;
            font-size: 18px;&#13;
            margin-top: 10px;&#13;
            margin-bottom: 10px;&#13;
            padding: 5px 5px 5px 5px;&#13;
            border-bottom: 1px solid #28a745;&#13;
        }&#13;
&#13;
        blockquote&amp;gt;p {&#13;
            font: 14px/22px normal helvetica, sans-serif;&#13;
            margin: 5px 0px 5px 0px;&#13;
            font-style:italic;&#13;
        }&#13;
&#13;
        .report-textarea{&#13;
            height: 100px!important;&#13;
        }&#13;
&#13;
        .preview{&#13;
            background: white;&#13;
            border-radius: 5px;&#13;
            color: black!important;&#13;
        }&#13;
&#13;
        .blockquote-comment{&#13;
            margin-top: 5px;&#13;
            border-left: 6px solid #6c6c6c;&#13;
            background: #6c757d;&#13;
            color: white;&#13;
            padding: 8px;&#13;
        }&#13;
&#13;
        .p-error-hint{&#13;
            color: #f94b43;&#13;
            display: none;&#13;
            font-weight: bold;&#13;
        }&#13;
&#13;
        .p-reply{&#13;
            color: #80bdff;&#13;
            margin-bottom: 0px;&#13;
        }&#13;
&#13;
        .div-comment-body{&#13;
            border-bottom: 1px solid #828286;&#13;
            padding-bottom: 6px;&#13;
        }&#13;
&#13;
        .div-gutter20{&#13;
            height: 20px;&#13;
        }&#13;
&#13;
        @media screen and (max-width: 567px){&#13;
            .div-gutter20{&#13;
                height: 5px;&#13;
            }&#13;
        }&#13;
&#13;
        .hr-margin-5{&#13;
            margin: 5px 0 5px 0!important;&#13;
        }&#13;
    &amp;lt;/style&amp;gt;&#13;
{% endblock %}&#13;
{% block title %}&#13;
    {{ post.title }}&#13;
{% endblock %}&#13;
&#13;
{% block content %}&#13;
    {{ moment.locale(auto_detect=True) }}&#13;
    &amp;lt;body&amp;gt;&#13;
    &amp;lt;main&amp;gt;&#13;
        &amp;lt;div class="container mt-2"&amp;gt;&#13;
            {% include "_flash.html" %}&#13;
            &amp;lt;ol class="breadcrumb"&amp;gt;&#13;
                &amp;lt;li class="breadcrumb-item"&amp;gt;&amp;lt;a class="text-decoration-none" href="/"&amp;gt;主页&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&#13;
                &amp;lt;li class="breadcrumb-item"&amp;gt;&amp;lt;a class="text-decoration-none" href="#"&amp;gt;杂谈&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;&#13;
                &amp;lt;li class="breadcrumb-item active"&amp;gt;帖子标题&amp;lt;/li&amp;gt;&#13;
            &amp;lt;/ol&amp;gt;&#13;
            &amp;lt;!-- 帖子主要信息 --&amp;gt;&#13;
            &amp;lt;div class="post-div"&amp;gt;&#13;
                &amp;lt;h1 class="post-title-h1" id="postTitle" data-id="{{ post.id }}"&amp;gt;{{ post.title }}&amp;lt;/h1&amp;gt;&#13;
                &amp;lt;div class="d-flex mb-0"&amp;gt;&#13;
                    &amp;lt;p class="text-muted mr-2 mb-0"&amp;gt;&amp;lt;i class="fa fa-thumbs-o-up mr-1"&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;span class="d-none d-lg-inline-block"&amp;gt;点亮&amp;lt;/span&amp;gt;({{ post.likes }}) &amp;lt;/p&amp;gt;&#13;
                    &amp;lt;p class="text-muted mr-2 mb-0"&amp;gt;&amp;lt;i class="fa fa-thumbs-o-down mr-1"&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;span class="d-none d-lg-inline-block"&amp;gt;点灭&amp;lt;/span&amp;gt;({{ -post.unlikes }}) &amp;lt;/p&amp;gt;&#13;
                    &amp;lt;p class="text-muted mr-2 mb-0"&amp;gt;&amp;lt;i class="fa fa-heart mr-1"&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;span class="d-none d-lg-inline-block"&amp;gt;收藏&amp;lt;/span&amp;gt;({{ post.collects }}) &amp;lt;/p&amp;gt;&#13;
                    &amp;lt;p class="text-muted mb-0"&amp;gt;&amp;lt;i class="fa fa-calendar-minus-o mr-1"&amp;gt;&amp;lt;/i&amp;gt;{{ post.create_time }}&amp;lt;/p&amp;gt;&#13;
                &amp;lt;/div&amp;gt;&#13;
                &amp;lt;div class="d-flex"&amp;gt;&#13;
                    {% if post.is_anonymous == 1 %}&#13;
                        &amp;lt;img src="{{ post.user.avatar }}" class="rounded avatar-50"&amp;gt;&#13;
                        &amp;lt;div class="d-flex"&amp;gt;&#13;
                            &amp;lt;div&amp;gt;&#13;
                                &amp;lt;a class="ml-2 text-decoration-none" href="{{ url_for('profile.index', user_id=post.user.id) }}"&amp;gt;{{ post.user.nickname }}&amp;lt;/a&amp;gt;&#13;
                                &amp;lt;div class="d-flex"&amp;gt;&#13;
                                    &amp;lt;a class="text-decoration-none ml-2"&amp;gt;&amp;lt;span class="badge badge-pill badge-success"&amp;gt;{{ post.user.college.name }}&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&#13;
                                    &amp;lt;a class="text-decoration-none ml-2"&amp;gt;&amp;lt;span class="badge badge-pill badge-info"&amp;gt;{{ post.user.role.name }}&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&#13;
                                &amp;lt;/div&amp;gt;&#13;
                            &amp;lt;/div&amp;gt;&#13;
                        &amp;lt;/div&amp;gt;&#13;
                    {% else %}&#13;
                        &amp;lt;img src="{{ url_for('static', filename='img/anonymous.jpeg') }}" class="rounded avatar"&amp;gt;&#13;
                        &amp;lt;div class="d-flex"&amp;gt;&#13;
                            &amp;lt;div&amp;gt;&#13;
                                &amp;lt;a class="ml-2 text-muted text-decoration-none"&amp;gt;匿名&amp;lt;/a&amp;gt;&#13;
                                &amp;lt;div class="d-flex"&amp;gt;&#13;
                                    &amp;lt;a class="text-decoration-none ml-2"&amp;gt;&amp;lt;span class="badge badge-pill badge-success"&amp;gt;{{ post.user.college.name }}&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&#13;
                                    &amp;lt;a class="text-decoration-none ml-2"&amp;gt;&amp;lt;span class="badge badge-pill badge-info"&amp;gt;{{ post.user.role.name }}&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;&#13;
                                &amp;lt;/div&amp;gt;&#13;
                            &amp;lt;/div&amp;gt;&#13;
                        &amp;lt;/div&amp;gt;&#13;
                    {% endif %}&#13;
                &amp;lt;/div&amp;gt;&#13;
                &amp;lt;div class="mt-2"&amp;gt;&#13;
                    &amp;lt;article&amp;gt;&#13;
                        {{ post.content|safe }}&#13;
                    &amp;lt;/article&amp;gt;&#13;
                &amp;lt;/div&amp;gt;&#13;
                {% if current_user.is_authenticated and current_user.id == post.user.id %}&#13;
                    &amp;lt;div class="d-flex flex-row-reverse mt-1"&amp;gt;&#13;
                        &amp;lt;a class="text-decoration-none text-muted" href="/post/edit/{{ post.id }}/"&amp;gt;&amp;lt;small&amp;gt;编辑&amp;lt;/small&amp;gt;&amp;lt;/a&amp;gt;&#13;
                        &amp;lt;a class="mr-2 text-decoration-none text-muted" href="/post/delete/{{ post.id }}/"&amp;gt;&amp;lt;small&amp;gt;删除&amp;lt;/small&amp;gt;&amp;lt;/a&amp;gt;&#13;
                    &amp;lt;/div&amp;gt;&#13;
                {% elif current_user.is_authenticated %}&#13;
                    &amp;lt;div class="d-flex flex-row-reverse mt-1"&amp;gt;&#13;
                        &amp;lt;a class="text-decoration-none text-muted ml-2" data-toggle="modal" data-target="#exampleModal" href="#"&amp;gt;&amp;lt;small&amp;gt;举报&amp;lt;/small&amp;gt;&amp;lt;/a&amp;gt;&#13;
                        &amp;lt;a class="text-decoration-none text-muted ml-2" href="/post/collect/{{ post.id }}/"&amp;gt;&#13;
                            &amp;lt;small&amp;gt;&#13;
                                {% if c_tag %}&#13;
                                    取消收藏&#13;
                                {% else %}&#13;
                                    收藏&#13;
                                {% endif %}&#13;
                            &amp;lt;/small&amp;gt;&#13;
                        &amp;lt;/a&amp;gt;&#13;
                        &amp;lt;a class="text-decoration-none text-muted ml-2" href="/post/unlike/{{ post.id }}/"&amp;gt;&amp;lt;small&amp;gt;点灭&amp;lt;/small&amp;gt;&amp;lt;/a&amp;gt;&#13;
                        &amp;lt;a class="text-decoration-none text-muted ml-2" href="/post/like/{{ post.id }}/"&amp;gt;&amp;lt;small&amp;gt;点亮&amp;lt;/small&amp;gt;&amp;lt;/a&amp;gt;&#13;
                    &amp;lt;/div&amp;gt;&#13;
                {% endif %}&#13;
            &amp;lt;/div&amp;gt;&#13;
            &amp;lt;div class="div-gutter20"&amp;gt;&amp;lt;/div&amp;gt;&#13;
        &amp;lt;/div&amp;gt;&#13;
    &amp;lt;/main&amp;gt;&#13;
    &amp;lt;/body&amp;gt;&#13;
{% endblock %}&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在该模板文件中,首先在&lt;code&gt;head&lt;/code&gt;块中定义了一些代码样式,然后将主要是内容写在&lt;code&gt;content&lt;/code&gt;块里面,在这个块里面做了下面几件事&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;显示顶部面包屑导航&lt;/li&gt;&#13;
	&lt;li&gt;展示帖子相关信息点亮、点灭、收藏数等等&lt;/li&gt;&#13;
	&lt;li&gt;如果帖子是匿名的方式发布的则隐藏发布者的相关信息&lt;/li&gt;&#13;
	&lt;li&gt;如果打开帖子的当前用户是该帖子的发帖者,则在帖子内容底部显示编辑、删除操作按钮,如果是其他用户则显示收藏、点亮、点灭按钮&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;&amp;nbsp;一般的,我们都会将style样式单独定义在一个css文件中,然后通过link标签来引用,由于是在开发过程中,如果样式需要频繁修改,则可以先定义在HTML文件中,因为如果定义在单独的css文件中,修改了样式需要清除浏览器缓存,不然样式不会更新。我们可以在调整好样式之后,将样式统一放入到一个css文件中去!&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;接下来,就需要对阅读帖子页面上的一些按钮进行功能开发了。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="3.帖子操作" name="3.帖子操作"&gt;&lt;/a&gt;3.帖子操作&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在第二小节中在阅读帖子页面中加入了很多功能按钮,对于帖子所有者有删除、编辑帖子功能按钮,对于其他访问者有点亮、点灭、收藏等按钮,在这一小节中就分别实现对应按钮的功能。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;a.点亮、点灭操作&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;打开&lt;code&gt;bbs/blueprint/frontend/post.py&lt;/code&gt;模块,将下面的代码块添加进去&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@post_bp.route('/like/&amp;lt;post_id&amp;gt;/', methods=['GET'])&#13;
@login_required&#13;
def like(post_id):&#13;
    post = Post.query.get_or_404(post_id)&#13;
    post.likes += 1&#13;
    db.session.commit()&#13;
    return redirect(url_for('.read', post_id=post_id))&#13;
&#13;
&#13;
@post_bp.route('/unlike/&amp;lt;post_id&amp;gt;/', methods=['GET'])&#13;
@login_required&#13;
def unlike(post_id):&#13;
    post = Post.query.get_or_404(post_id)&#13;
    post.unlikes += 1&#13;
    db.session.commit()&#13;
    return redirect(url_for('.read', post_id=post_id))&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;两个视图函数的功能分别是将帖子的点亮数、点灭数+1操作,其实这里做有点不严谨,因为没有绑定用户的id,只是单纯的进行+1的操作,那么用户可以无限点击,要绑定用户id还需要增加表,就交给聪明的读者来实现吧,在下一小节收藏、取消收藏操作中实现了这个功能,可以参照一下代码,如果没有实现思路,可以参照一下下面的代码。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;b.收藏、取消收藏操作&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;跟大多数论坛一样,当用户看到一个比较感兴趣的帖子之后,可以通过收藏该帖子,如果下次用户还想去浏览该帖子,就不需要导出去找,同样的我们也可以实现此功能。打开&lt;code&gt;bbs/blueprint/frontend/post.py&lt;/code&gt; 模块,添加进下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@post_bp.route('/collect/&amp;lt;post_id&amp;gt;/')&#13;
@login_required&#13;
def collect(post_id):&#13;
    post_collect(post_id)&#13;
    return redirect(url_for('.read', post_id=post_id))&#13;
&#13;
&#13;
def post_collect(post_id):&#13;
    post = Post.query.get_or_404(post_id)&#13;
    c = Collect.query.filter(Collect.user_id == current_user.id, Collect.post_id == post_id).first()&#13;
    if c:&#13;
        post.collects -= 1&#13;
        db.session.delete(c)&#13;
        flash('取消收藏帖子成功!', 'success')&#13;
    else:&#13;
        post.collects += 1&#13;
        c = Collect(user_id=current_user.id, post_id=post_id)&#13;
        db.session.add(c)&#13;
        flash('收藏帖子成功!', 'success')&#13;
        &#13;
    db.session.commit()&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在上面的代码中,出现了一个新的模型类Collect,该类就是用来存储用户收藏帖子的数据库表,这张表属于多对多关系的中间表。因为一个用户可以收藏多个帖子,而一个帖子又可以被多个用户收藏,因此就需要通过一张中间表来存储用户与帖子之间的关系,ER图如下所示&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="er图" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/8439%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_68.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;打开&lt;code&gt;bbs/models.py&lt;/code&gt;模块,新建&lt;code&gt;Collect&lt;/code&gt;模型类,代码清单如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class Collect(db.Model):&#13;
    __tablename__ = 't_collect'&#13;
&#13;
    id = db.Column(db.INTEGER, primary_key=True, autoincrement=True)&#13;
    user_id = db.Column(db.INTEGER, db.ForeignKey('t_user.id'))&#13;
    post_id = db.Column(db.INTEGER, db.ForeignKey('t_post.id'))&#13;
    timestamps = db.Column(db.DateTime, default=datetime.datetime.now)&#13;
&#13;
    user = db.relationship('User', back_populates='collect', lazy='joined')&#13;
    post = db.relationship('Post', back_populates='collect', lazy='joined')&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;回到前一段代码,先通过传过来的&lt;code&gt;post_id&lt;/code&gt;参数以及&lt;code&gt;当前登录用户的id&lt;/code&gt;在&lt;code&gt;t_collect&lt;/code&gt;表中查找是否包含这个数据,如果包含,则说明用户点击的是取消收藏的按钮,反之则表明用户点击的是收藏按钮,按照对应的方式处理插入数据还是增加数据。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;c.帖子编辑&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;帖子编辑功能是每个论坛都必须提供的功能,接下来就实现此功能,打开bbs/forms.py模块,加入下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class EditPostForm(BasePostForm):&#13;
    submit = SubmitField(u'保存编辑', render_kw={'class': 'source-button btn btn-danger btn-xs mt-2 text-right'})&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;同样的在&lt;code&gt;bbs/blueprint/frontend/post.py&lt;/code&gt;模块加入下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@post_bp.route('/edit/&amp;lt;post_id&amp;gt;/', methods=['GET', 'POST'])&#13;
@login_required&#13;
def edit(post_id):&#13;
    form = EditPostForm()&#13;
    post = Post.query.get_or_404(post_id)&#13;
    if form.validate_on_submit():&#13;
        title = form.title.data&#13;
        ep = Post.query.filter_by(title=title).first()&#13;
&#13;
        # 修改标题不能是其他已经存在帖子的标题&#13;
        if ep and ep.id != post.id:&#13;
            flash('该帖子标题已经存在', 'danger')&#13;
            return redirect(url_for('.edit', post_id=post_id))&#13;
&#13;
        cate = form.category.data&#13;
        anonymous = form.anonymous.data&#13;
        content = form.body.data&#13;
        post.title = title&#13;
        post.cate_id = cate&#13;
        post.is_anonymous = anonymous&#13;
        post.content = content&#13;
        db.session.commit()&#13;
        flash('帖子编辑成功!', 'success')&#13;
        return redirect(url_for('.read', post_id=post_id))&#13;
&#13;
    form.title.data = post.title&#13;
    form.body.data = post.content&#13;
    form.category.data = post.cate_id&#13;
    form.anonymous.data = post.is_anonymous&#13;
    return render_template('frontend/post/edit-post.html', post=post, form=form)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在上面的代码中,使用了EditPostForm这个表单类,通过继承自BasePostForm,就如同用户发表帖子那一节一样。之后我们根据请求参数中post_id来从数据库中获取对应的帖子内容,如果是提交表单请求的操作就进行帖子内容的更新,如果是编辑表单的请求操作就将该帖子的内容渲染在模板文件中,打开bbs/templates/frontend/post/目录,新建edit-post.html模板文件,嵌入下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;{% extends "frontend/base.html" %}&#13;
{% import 'bootstrap/wtf.html' as wtf %}&#13;
&#13;
{% block head %}&#13;
    {{ super() }}&#13;
&#13;
{% endblock %}&#13;
{% block title %}&#13;
    编辑帖子-{{ post.title }}&#13;
{% endblock %}&#13;
{% block content %}&#13;
&#13;
    &amp;lt;body&amp;gt;&#13;
    &amp;lt;main&amp;gt;&#13;
        &amp;lt;div class="container mt-3"&amp;gt;&#13;
            {% include "_flash.html" %}&#13;
            &amp;lt;h3 class="text-info"&amp;gt;&amp;lt;strong&amp;gt;编辑帖子&amp;lt;/strong&amp;gt;&amp;lt;/h3&amp;gt;&#13;
            &amp;lt;hr class="bg-secondary"&amp;gt;&#13;
            &amp;lt;form action="/post/edit/{{ post.id }}/" method="post"&amp;gt;&#13;
                {{ form.csrf_token }}&#13;
                {{ wtf.form_field(form.title) }}&#13;
                &amp;lt;div class="row"&amp;gt;&#13;
                    &amp;lt;div class="col"&amp;gt;&#13;
                        {{ wtf.form_field(form.category) }}&#13;
                    &amp;lt;/div&amp;gt;&#13;
                    &amp;lt;div class="col"&amp;gt;&#13;
                        {{ wtf.form_field(form.anonymous) }}&#13;
                    &amp;lt;/div&amp;gt;&#13;
                &amp;lt;/div&amp;gt;&#13;
                {{ form.body }}&#13;
                &amp;lt;div class="text-right"&amp;gt;&#13;
                    {{ form.submit }}&#13;
                &amp;lt;/div&amp;gt;&#13;
            &amp;lt;/form&amp;gt;&#13;
        &amp;lt;/div&amp;gt;&#13;
    &amp;lt;/main&amp;gt;&#13;
    &amp;lt;/body&amp;gt;&#13;
    {{ ckeditor.load() }}&#13;
    {{ ckeditor.config(name='body') }}&#13;
    &amp;lt;script&amp;gt;&#13;
        CKEDITOR.on( 'instanceReady', function( evt ) {&#13;
            evt.editor.dataProcessor.htmlFilter.addRules( {&#13;
                elements: {&#13;
                    img: function(el) {&#13;
                        el.addClass('img-fluid d-block mx-auto');&#13;
                    },&#13;
                    table: function (el){&#13;
                        el.addClass('table table-responsive');&#13;
                    },&#13;
                    thead: function (el){&#13;
                        el.addClass('thead-light');&#13;
                    },&#13;
                    blockquote: function (el){&#13;
                        el.addClass('m-blockquote');&#13;
                    }&#13;
                }&#13;
            });&#13;
        });&#13;
    &amp;lt;/script&amp;gt;&#13;
{% endblock %}&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;跟发布帖子那一节差不多,只是在添加了一些&lt;code&gt;JavaScript&lt;/code&gt;代码,这些代码的主要作用是用来定制一些&lt;code&gt;CKEditor&lt;/code&gt;元素的样式的,具体可以去查看&lt;code&gt;CKEditor4&lt;/code&gt;的文档,这里不进行细说。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;d.删除帖子&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;删除帖子的功能相对来说就十分的简单了,这里的删除实际上不是在数据库中删除,而是将帖子设置为某种状态,在很多互联网企业中删除都是通过这种方式来实现的,打开&lt;code&gt;bbs/blueprint/frontend/post.py&lt;/code&gt;模块,添加下面的代码清单&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@post_bp.route('/delete/&amp;lt;post_id&amp;gt;/', methods=['GET'])&#13;
@login_required&#13;
def delete(post_id):&#13;
    post = Post.query.get_or_404(post_id)&#13;
    if post.can_delete():&#13;
        post.status_id = 2&#13;
        db.session.commit()&#13;
        flash('帖子删除成功!', 'success')&#13;
    else:&#13;
        flash('不是你的东西,你没有权限删除!', 'danger')&#13;
    return redirect(url_for('index_bp.index'))&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;通过请求参数post_id 从数据库中获取对应post,通过&lt;code&gt;can_delete()&lt;/code&gt;方法来判断当前帖子是否属于当前用户,如果不是则返回提示消息,如果是则将当前帖子的&lt;code&gt;status_id&lt;/code&gt;置为2,然后保存到数据库中,返回提示消息。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;至此,本节内容就全部讲完啦,整个项目差不多已经写完了,可以到&lt;a href="https://github.com/weijiang1994/university-bbs"&gt;github仓库&lt;/a&gt;下载源码对照我博客网站上的教程阅读。读者对照教程一边看教程一边写代码可能会出错哦,因为教程是在我代码写完之后开始写的,可能有些地方会遗漏,请谅解~哈哈哈~&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/33/" rel="alternate"/>
  </entry>
  <entry>
    <id>32</id>
    <title>[应用部署]记一次网站被高频爬虫攻击</title>
    <updated>2023-09-27T02:08:19.929257+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;1.问题&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;昨天下午(2021-02-22)4点多的时候,访问网站发现打开速度非常的慢,个人感觉有点不正常。虽然说部署服务器的带宽只有1M,但是首页的日访问量一般都是100-200之间,其中还包括我本人的访问量,因此感觉是有IP一直占用这服务器带宽,导致网站打开十分缓慢。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;2.排查&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;通过SSH登录到云服务器上,发现SSH也变得十分卡顿,这一般都是因为网络出口带宽太低或者被占用,平时通过SSH操作的时候都十分流畅,毕竟买的是鹅肠成都地区的服务器,通过如下命令查看网络流量详情&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo iftop &lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;iftop不是系统自带工具需要安装&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;sudo apt-get install iftop&amp;nbsp;&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;p&gt;因为服务器配置低只有一个网卡,如果有多个网卡可以通过-i参数来指定哪个网卡&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;sudo iftop -i eth0&amp;nbsp;&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;流量详情如下图&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="iftop" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/6457%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_66.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;可以看到IP&lt;code&gt;116.253.54.18&lt;/code&gt;一直有网络数据交换,而且瞬时数据量非常大(虽然说只有1M多点但对于我这个IM带宽的服务器已经是很巨大了),经过IP查询该IP为广西贺州,如下图&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/5147%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20210223093031.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;3.解决办法&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;那么查明了问题的根源解决办法就出来了,可以封禁该IP来解决这个问题,现在是我的SSH很卡没办法使用,我们可以通过鹅肠云服务器安全组策略来禁止该IP。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;a.通过网页控制台&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;登录鹅肠云服务器控制台,点击左侧安全组选项,在右侧页面中点击&lt;code&gt;蓝色新建&lt;/code&gt;按钮,模板选择自定义,其他的默认设置就好。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/2166image-20210223093525078.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;点击确认按钮然后弹出提示框点击立即设置安全规则,在入站规则中添加规则,按照下面的提示进行设置即可,然后点击完成。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="image-20210223093757362" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/3249image-20210223093757362.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;到现在只是添加了安全规则,但是并没有在你的云服务器实例上生效,在点击页面上关联实例tab,将新建的安全规则关联到对应的实例上,然后点击确定,这时候你禁用的IP就访问不了你的服务器了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="image-20210223094114780" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/2291image-20210223094114780.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;b.通过nginx封禁IP&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;上面那种方式是在SSH没办法正常使用的情况下的解决方法,如果SSH能够正常使用的话,那么可以通过nginx的一些机制来禁用访问IP。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;假设nginx安装在&lt;code&gt;/etc/nginx/&lt;/code&gt;目录下,在该目录中新建黑名单IP配置文件&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo vim blacklist.conf &lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在&lt;code&gt;blacklist.conf&lt;/code&gt;中按照下面的方式写入内容&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-ini"&gt;禁用IP&#13;
deny 116.253.54.18;&#13;
禁用IP段&#13;
deny 116.253.54.0/225; &lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;保存退出文件,输入下面的命令配置nginx.conf&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo vim nginx.conf&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在&lt;code&gt;http&lt;/code&gt;配置段中加入下面的内容&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-nginx"&gt;include blacklist.conf;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;测试配置是否正确&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo nginx -t&#13;
# 出现下面的提示则说明配置正确&#13;
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok&#13;
nginx: configuration file /etc/nginx/nginx.conf test is successful &lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;重启nginx配置&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo nginx -s reload&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;blacklist.conf文件可以新建在任何地方,在ngxin.conf中引用的时候加入绝对路径就可以了;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;nginx封禁IP只是将响应返回403并没有真正的封禁IP;&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;当然除了上面的两种方式还可以通过防火墙等其他方式来解决这个问题,这篇文章只说明我个人的处理方式。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;网站恢复正常访问之后打开运行日志发现这个B发起了6800多次请求,如下图&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/6003%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20210223101858.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/32/" rel="alternate"/>
  </entry>
  <entry>
    <id>31</id>
    <title>[前端开发]详解HTML聊天对话框的实现</title>
    <updated>2023-09-27T02:08:19.929230+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;1.看看长啥样&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;我截取了本站&lt;a href="https://2dogz.cn/timeline/"&gt;建站日志&lt;/a&gt;的样式如下图所示&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="示例图片" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/2720%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_54.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;其详细的css代码如下,左边的小箭头其实就是通过&lt;span class="marker"&gt;:before&lt;/span&gt;这个伪元素来实现的。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-css"&gt;.cd-timeline-content {&#13;
    position: relative;&#13;
    margin-left: 60px;&#13;
    background: #303030;&#13;
    border-radius: 0.25em;&#13;
    padding: 1em;&#13;
    box-shadow: 0 3px 0 #202020;&#13;
}&#13;
&#13;
.cd-timeline-content:before {&#13;
    content: '';&#13;
    position: absolute;&#13;
    top: 16px;&#13;
    right: 100%;&#13;
    height: 0;&#13;
    width: 0;&#13;
    border: 7px solid transparent;&#13;
    border-right: 7px solid #303030;&#13;
}&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;现在我们先不讨论下面的代码,我们先来看看其他的知识点。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;2. :before&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;before顾名思义就是在什么之前,与之对应的是&lt;span class="marker"&gt;:after&lt;/span&gt;伪元素,通过该伪元素可以在目标元素前加入某些内容,看看下面的示例&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;&amp;lt;style&amp;gt;&#13;
.hello:before{&#13;
    content: "Hello ";&#13;
}&#13;
.hello:after{&#13;
    content: "!";&#13;
}&#13;
.hello{&#13;
    color: white;&#13;
}&#13;
&amp;lt;/style&amp;gt;&#13;
&#13;
&amp;lt;body&amp;gt;&#13;
    &amp;lt;p class="hello"&amp;gt;World&amp;lt;/p&amp;gt;&#13;
    &amp;lt;p class="hello"&amp;gt;Tom&amp;lt;/p&amp;gt;&#13;
    &amp;lt;p class="hello"&amp;gt;Jack&amp;lt;/p&amp;gt;&#13;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="hello" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/5750%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_56.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;可以看到在每个p标签前面加上了&lt;code&gt;Hello&lt;/code&gt; 在后面加上了&lt;code&gt;!&lt;/code&gt;,通过伪元素&lt;code&gt;:before&lt;/code&gt;和&lt;code&gt;:after&lt;/code&gt;来实现的。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;3.position属性&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;position字面意思是位置,在css中position有五个值&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;static (默认值)&lt;/li&gt;&#13;
	&lt;li&gt;relative&lt;/li&gt;&#13;
	&lt;li&gt;fixed&lt;/li&gt;&#13;
	&lt;li&gt;absolute&lt;/li&gt;&#13;
	&lt;li&gt;sticky&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;p&gt;在上面的代码中使用到了relative以及absolute,这里就初略讲一个两个属性的功能。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;a.relative&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;相对定位是一个非常容易掌握的概念。如果对一个元素进行相对定位,它将出现在它所在的位置上。然后,可以通过设置垂直或水平位置,让这个元素&amp;ldquo;相对于&amp;rdquo;它的起点进行移动。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;如果将 top 设置为 20px,那么框将在原位置顶部下面 20 像素的地方。如果 left 设置为 30 像素,那么会在元素左边创建 30 像素的空间,也就是将元素向右移动。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-css"&gt;.box_relative {&#13;
  position: relative;&#13;
  left: 30px;&#13;
  top: 20px;&#13;
}&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="relative" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/2675%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_55.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;b.absolute&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;绝对定位的元素的位置相对于最近的已定位父元素,如果元素没有已定位的父元素,那么它的位置相对于&amp;lt;html&amp;gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-css"&gt;.box_absolute {&#13;
  position: absolute;&#13;
  left: 30px;&#13;
  top: 20px;&#13;
}&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="absolute" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/2600%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_57.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;4.border属性&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;相信很多小伙伴都很疑惑那个三角形是怎么来的,其实就是通过border以及border-right来实现的,听我慢慢道来。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;border就是给容易添加一个外边框,可以看看如下的例子&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;&amp;lt;style type="text/css"&amp;gt;&#13;
.border-gray{&#13;
    height: 50px;&#13;
    width: 50px;&#13;
    background-color: white;&#13;
    border: 4px solid #aaaaaa;&#13;
}&#13;
&amp;lt;/style&amp;gt;&#13;
&amp;lt;body&amp;gt;&#13;
&amp;lt;div class="border-gray"&amp;gt;&amp;lt;/div&amp;gt;&#13;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/2100%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_59.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;我们可以清楚的看到在div加了一个灰色的外边框,接下来我们将四个方向的边框颜色修改一下,并且为了更加直观,将边框的宽度改为10px;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;&amp;lt;style type="text/css"&amp;gt;&#13;
.border-gray{&#13;
    height: 50px;&#13;
    width: 50px;&#13;
    background-color: white;&#13;
    border-top: 10px solid red;&#13;
    border-right: 10px solid blue;&#13;
    border-left: 10px solid green;&#13;
    border-bottom: 10px solid cayon;&#13;
}&#13;
&amp;lt;/style&amp;gt;&#13;
&amp;lt;body&amp;gt;&#13;
&amp;lt;div class="border-gray"&amp;gt;&amp;lt;/div&amp;gt;&#13;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="10px" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/1131%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_60.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;这下小伙伴们应该有点思路了吧关于小三角形怎么形成的。我们可以清楚的看到四个方向边框交界处是通过45&amp;deg;进行划分的,也就是所在四个角相邻的两个边框各占一半,边框的长度就是对应水平或者垂直方向的长度,如果到这里还不明白的话我们继续修改例子。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;&amp;lt;style type="text/css"&amp;gt;&#13;
.border-gray{&#13;
    height:0;&#13;
    width: 0;&#13;
    background-color: white;&#13;
    border-top: 30px solid red;&#13;
    border-right: 30px solid blue;&#13;
    border-left: 30px solid green;&#13;
    border-bottom: 30px solid cadetblue;&#13;
}&#13;
&amp;lt;/style&amp;gt;&#13;
&amp;lt;body&amp;gt;&#13;
&amp;lt;div class="border-gray"&amp;gt;&amp;lt;/div&amp;gt;&#13;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="30px" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/8708%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_61.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;我们将div的长宽都设置为0,那么边框的长度不就都变为了0么?那么同一个方向两个角直接就没有长度了,所以就形成了三角形的样子,如图上所示的,我们可以清楚的看到四个三角形,如果我们需要保留哪个方向的三角形我们将其他方向边框颜色设置为透明就可以啦。&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;保留top&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;&amp;lt;style type="text/css"&amp;gt;&#13;
.border-gray{&#13;
    height:0;&#13;
    width: 0;&#13;
    background-color: white;&#13;
    border: 30px solid transparent;&#13;
    border-top: 30px solid red;&#13;
}&#13;
&amp;lt;/style&amp;gt;&#13;
&amp;lt;body&amp;gt;&#13;
&amp;lt;div class="border-gray"&amp;gt;&amp;lt;/div&amp;gt;&#13;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;保留bottom&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;&amp;lt;style type="text/css"&amp;gt;&#13;
.border-gray{&#13;
    height:0;&#13;
    width: 0;&#13;
    background-color: white;&#13;
    border: 30px solid transparent;&#13;
    border-bottom: 30px solid red;&#13;
}&#13;
&amp;lt;/style&amp;gt;&#13;
&amp;lt;body&amp;gt;&#13;
&amp;lt;div class="border-gray"&amp;gt;&amp;lt;/div&amp;gt;&#13;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;保留right&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;&amp;lt;style type="text/css"&amp;gt;&#13;
.border-gray{&#13;
    height:0;&#13;
    width: 0;&#13;
    background-color: white;&#13;
    border: 30px solid transparent;&#13;
    border-right: 30px solid red;&#13;
}&#13;
&amp;lt;/style&amp;gt;&#13;
&amp;lt;body&amp;gt;&#13;
&amp;lt;div class="border-gray"&amp;gt;&amp;lt;/div&amp;gt;&#13;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;保留left&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;&amp;lt;style type="text/css"&amp;gt;&#13;
.border-gray{&#13;
    height:0;&#13;
    width: 0;&#13;
    background-color: white;&#13;
    border: 30px solid transparent;&#13;
    border-left: 30px solid red;&#13;
}&#13;
&amp;lt;/style&amp;gt;&#13;
&amp;lt;body&amp;gt;&#13;
&amp;lt;div class="border-gray"&amp;gt;&amp;lt;/div&amp;gt;&#13;
&amp;lt;/body&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="result" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/9422%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_63.png" /&gt;&amp;nbsp;&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;注意border必须在border-* 前面定义,如果border定义在border-*后面则会覆盖掉border-*的样式&lt;/li&gt;&#13;
	&lt;li&gt;需要哪个朝向的箭头则定义相反方向的border-*,如需要left朝向的箭头则定义border-right&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;h1&gt;5.搞一个试试&lt;/h1&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;&amp;lt;!DOCTYPE html&amp;gt;&#13;
&amp;lt;html lang="en"&amp;gt;&#13;
&amp;lt;head&amp;gt;&#13;
    &amp;lt;meta charset="UTF-8"&amp;gt;&#13;
    &amp;lt;title&amp;gt;Title&amp;lt;/title&amp;gt;&#13;
    &amp;lt;style type="text/css"&amp;gt;&#13;
        .chat{&#13;
            position: relative;&#13;
            min-height: 45px;&#13;
            width: 200px;&#13;
            border-radius: 3px;&#13;
            padding: 3px;&#13;
            background-color: #eeeeee;&#13;
            font-size: 14px;&#13;
        }&#13;
&#13;
        .chat&amp;gt;p{&#13;
            margin: 0;&#13;
        }&#13;
&#13;
        .chat:before{&#13;
            content: '';&#13;
            position: absolute;&#13;
            right: 100%;&#13;
            top: 15px;&#13;
            height: 0;&#13;
            width: 0;&#13;
            border: 7px solid transparent;&#13;
            border-right: 7px solid #eeeeee;&#13;
        }&#13;
    &amp;lt;/style&amp;gt;&#13;
&amp;lt;/head&amp;gt;&#13;
&#13;
&amp;lt;body&amp;gt;&#13;
&amp;lt;div class="chat"&amp;gt;&#13;
    &amp;lt;p&amp;gt;白日依山尽,黄河入海流。欲穷千里目,更上一层楼。&amp;lt;/p&amp;gt;&#13;
&amp;lt;/div&amp;gt;&#13;
&amp;lt;/body&amp;gt;&#13;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&lt;img alt="sample" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/9787%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_65.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/31/" rel="alternate"/>
  </entry>
  <entry>
    <id>30</id>
    <title>[MySQL]MySQL学习摘要之管理常用命令</title>
    <updated>2023-09-27T02:08:19.929203+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;1.权限生效时间&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;⽤户及权限信息放在库名为mysql的库中, mysql启动时,这些内容被读进内存并且从此时⽣效,所以如果通过直接操作这些表来修改⽤户及权限信息的,需要重启mysql或者执⾏flush privileges;才可以⽣效。&lt;br /&gt;&#13;
⽤户登录之后, mysql会和当前⽤户之间创建⼀个连接,此时⽤户相关的权限信息都保存在这个连接中,存放在内存中,此时如果有其他地⽅修改了当前⽤户的权限,这些变更的权限会在下⼀次登录时才会⽣效。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;2.查看mysql所有用户&lt;/h1&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;use mysql&#13;
select user, host from user;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;结果:&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="alluser" class="d-block img-fluid mx-auto pic" src="/backend/files/8071%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_47.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;3.创建用户&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;语法:&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;create user 用户名[@主机名] [identified by '密码']&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;主机名默认值为%,%意味着该用户可以从任何主机连接MySQL服务器&lt;/li&gt;&#13;
	&lt;li&gt;密码可以省略,表示无密码登录&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;h2&gt;a.不指定主机名&lt;/h2&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;use mysql; &#13;
create user demo;&#13;
select user, host from user;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;结果:&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="创建user" class="d-block img-fluid mx-auto pic" src="/backend/files/9468%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_48.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;上面创建的用户&lt;span class="marker"&gt;demo&lt;/span&gt;没有设置主机以及登录密码,因此可以无密码登录MySQL服务器,通过demo用户登录数据库。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;mysql -udemo&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h2&gt;b.指定主机密码&lt;/h2&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;# 指定主机&#13;
use mysql;&#13;
create user 'user2'@'localhost';&#13;
# 指定主机密码&#13;
create user 'user3'@'127.0.0.1' identified by '123456';&#13;
# 任意主机指定密码&#13;
create user 'user4'@'%' identified by '123456';&#13;
# 指定IP段&#13;
create user 'user5'@'192.168.1.%' identified by '123456';&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;user2只能在本机登录可以通过无密码的方式&lt;/li&gt;&#13;
	&lt;li&gt;user3只能在本机登录并且需要指定密码&lt;/li&gt;&#13;
	&lt;li&gt;user4可以在任何机器上登录需要指定密码&lt;/li&gt;&#13;
	&lt;li&gt;user5只能在IP段为192.168.1的机器上登录&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;h1&gt;4.修改登录密码&lt;/h1&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;通过管理员修改&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;SET PASSWORD FOR '用户名'@'主机'=PASSWORD('密码');&lt;/code&gt;&lt;/pre&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;create user&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;create user 用户名[@主机名][identified by '密码'];&#13;
set password = password('密码');&lt;/code&gt;&lt;/pre&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;通过mysql.user表修改&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;use mysql;&#13;
update user set authentication_string = password('321') where user =&#13;
'test1' and host = '%';&#13;
flush privileges;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
	&lt;div class="m-block-text" style="background:#eeeeee;border:1px solid #cccccc;padding:5px 10px;"&gt;通过表的⽅式修改之后,需要执⾏flush privileges;才能对⽤户⽣效。&lt;br /&gt;&#13;
	MySQL5.7版本中user表中的authentication_string字段表⽰密码,⽼的⼀些版本中密码字段是password。&lt;/div&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&#13;
&lt;h1&gt;&amp;nbsp;5.用户授权&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在创建了新的用户之后,如果不给用户授权那没有太多的实际意义。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;语法:&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;grant privileges ON database.table TO 'username'@'host' with grant option&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;说明:&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;privileges可以是all,也可以是多个权限比如select、update等,使用&amp;#39;,&amp;#39;隔开;&lt;/li&gt;&#13;
	&lt;li&gt;ON&amp;nbsp;&amp;nbsp; &amp;nbsp;⽤来指定权限针对哪些库和表,格式为数据库.表名 ,点号前⾯⽤来指定数据库名,点号后⾯⽤来指定表名, *.* 表⽰所有数据库所有表;&lt;/li&gt;&#13;
	&lt;li&gt;TO&amp;nbsp;&amp;nbsp; &amp;nbsp;表⽰将权限赋予某个⽤户,&amp;nbsp;&amp;nbsp; &amp;nbsp;格式为username@host, @前⾯为⽤户名, @后⾯接限制的主机,可以是IP、 IP段、域名以及%, %表⽰任何地⽅;&lt;/li&gt;&#13;
	&lt;li&gt;WITH&amp;nbsp;&amp;nbsp; &amp;nbsp;GRANT&amp;nbsp;&amp;nbsp; &amp;nbsp;OPTION&amp;nbsp;&amp;nbsp; &amp;nbsp;这个选项表⽰该⽤户可以将⾃⼰拥有的权限授权给别⼈。注意:经常有⼈在创建操作⽤户的时候不指定WITH&amp;nbsp;&amp;nbsp; &amp;nbsp;GRANT&amp;nbsp;&amp;nbsp; &amp;nbsp;OPTION选项导致后来该⽤户不能使⽤GRANT命令创建⽤户或者给其它⽤户授权。 备注:可以使⽤GRANT重复给⽤户添加权限,权限叠加,⽐如你先给⽤户添加⼀个select权限,然后又给⽤户添加⼀个insert权限,那么该⽤户就同时拥有了select和insert权限;&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;示例:&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;# 给user1授权可以操作所有库所有权限&#13;
grant all on *.* to 'user1'@'%';&#13;
# 给user2授权demo库中所有表的select权限&#13;
grant select on demo.* to 'user2'@'%';&#13;
# 给user3授权demo库所有标的select、update权限&#13;
grant select,update on demo.* to 'user3'@'%';&#13;
# user4只能查询mysql.user的user,host字段&#13;
grant select(host, user) on mysql.user 'user4'@'%';&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h1&gt;6.查看权限&lt;/h1&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;# 查看当前登录用户权限&#13;
show grants;&#13;
# 查看指定用户权限&#13;
show grants for 'user1'@'localhost';&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="all priliveges" class="d-block img-fluid mx-auto pic" src="/backend/files/6353%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_50.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;7.删除用户权限&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;语法:&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-php"&gt;revoke privileges ON database.table FROM '用户名'@'主机名';&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;示例:&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;# 查看用户权限&#13;
show grants for 'user2'@'localhost';&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="revoke" class="d-block img-fluid mx-auto pic" src="/backend/files/1164%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_51.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;# 删除mysql.user表的select(host)权限&#13;
revoke select(host) on mysql.user from 'user2'@'localhost';&#13;
# 查看user2现有权限&#13;
show grants for 'user2'@'localhost';&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="revoke" class="d-block img-fluid mx-auto pic" src="/backend/files/4697%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_52.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;根据结果可以看到&lt;span class="marker"&gt;user2&lt;/span&gt;的&lt;span class="marker"&gt;select(host)&lt;/span&gt;权限已经不存在了。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;8.删除用户&lt;/h1&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;drop命令&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;drop user '用户名'@'主机';&#13;
drop user 'user2'@'localhost';&lt;/code&gt;&lt;/pre&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;mysql.user表&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-sql"&gt;delete from user where user='⽤户名' and host='主机';&#13;
flush privileges;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
	&lt;blockquote class="m-blockquote"&gt;&#13;
	&lt;p&gt;注意通过表的⽅式删除的,需要调⽤flush privileges;刷新权限信息(权限启动的时候在内存中保存着,通过表的⽅式修改之后需要刷新⼀下)。&lt;/p&gt;&#13;
	&lt;/blockquote&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/30/" rel="alternate"/>
  </entry>
  <entry>
    <id>29</id>
    <title>[应用部署]给ngxin服务器添加HTTPS</title>
    <updated>2023-09-27T02:08:19.929175+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;&lt;a id="1.获取免费的SSL证书" name="1.获取免费的SSL证书"&gt;&lt;/a&gt;1.获取免费的SSL证书&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在部署HTTPS之前,我们需要先获取SSL证书,我购买的是鹅肠的云服务器,可以申请免费证书,点击这个链接进行申请&lt;a href="https://console.cloud.tencent.com/ssl"&gt;鹅肠SSL证书申请&lt;/a&gt;,按照界面的上的操作步骤进行操作就可以了,审核通过之后,下载证书即可,下载的压缩文件中包含的文件如下图所示,具体看你使用的哪款服务器进行部署的,自行选择,我这里选择的ngxin服务器。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="ssl证书" class="d-block img-fluid mx-auto pic" src="/backend/files/252%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_45.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="2.拷贝SSL证书" name="2.拷贝SSL证书"&gt;&lt;/a&gt;2.拷贝SSL证书&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;下载好证书之后解压出来,将证书文件拷贝到服务器中的目标文件夹中。我这里使用ngxin作为示例,一般存放在&lt;/p&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;/etc/nginx/cert/ssl_name.crt&lt;/li&gt;&#13;
	&lt;li&gt;/etc/nginx/cert/ssl_name.key&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&#13;
&lt;p&gt;其中&lt;span class="marker"&gt;cert&lt;/span&gt;目录是不存在的而且命名可以随意命名,但是建议使用望文生义的名称,这两个路径十分重要,后续的配置中需要用到。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="3.配置nginx" name="3.配置nginx"&gt;&lt;/a&gt;3.配置nginx&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;打开ngxin的配置文件&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo vim /etc/nginx/sites-avaliable/default&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在你需要的&lt;span class="marker"&gt;Server&lt;/span&gt;块中修改为下面内容&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-nginx"&gt;server{&#13;
    # listen 80 default_server;&#13;
    # listen [::]:80 default_server;&#13;
&#13;
    # SSL configuration&#13;
    #&#13;
    # listen 443 ssl default_server;&#13;
    # listen [::]:443 ssl default_server;&#13;
&#13;
    listen 443;&#13;
    ssl on;&#13;
    ssl_certificate /etc/nginx/cert/1_2dogz.cn_bundle.crt;&#13;
    ssl_certificate_key /etc/nginx/cert/2_2dogz.cn.key;&#13;
    ssl_session_timeout 5m;&#13;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; &#13;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; &#13;
    ssl_prefer_server_ciphers on;&#13;
}&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;上面的内容为配置HTTPS,server块中的其他内容不能删除掉!&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="4.配置重定向" name="4.配置重定向"&gt;&lt;/a&gt;4.配置重定向&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;因为原来采用的http的方式访问,经过上述配置之后采用http方式访问就会失效,因此我们需要将http访问方式重定向到https上面来,同样在上一小节的配置文件中加入如下内容&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-nginx"&gt;server {&#13;
    listen 80;&#13;
    server_name 2dogz.cn;&#13;
&#13;
    rewrite ^(.*)$  https://$host$1 permanent;&#13;
}&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;其中server_name是你自己网站的域名。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;这样我们的网站就可以通过https的方式访问啦,如下图,在浏览器的地址栏会显示一个锁的图标,点击即可以看到相关信息&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="https访问" class="d-block img-fluid mx-auto pic" src="/backend/files/2674%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_46.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;&lt;a id="5.配置HTTP2" name="5.配置HTTP2"&gt;&lt;/a&gt;5.配置HTTP2&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;配置HTTP2协议其实非常的简单,只需要在配置文件监听端口部分加入下面的配置即可&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-nginx"&gt;server{&#13;
    # listen 80 default_server;&#13;
    # listen [::]:80 default_server;&#13;
&#13;
    # SSL configuration&#13;
    #&#13;
    # listen 443 ssl default_server;&#13;
    # listen [::]:443 ssl default_server;&#13;
&#13;
    listen 443 ssl http2;&#13;
    ssl on;&#13;
    ssl_certificate /etc/nginx/cert/1_2dogz.cn_bundle.crt;&#13;
    ssl_certificate_key /etc/nginx/cert/2_2dogz.cn.key;&#13;
    ssl_session_timeout 5m;&#13;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; &#13;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; &#13;
    ssl_prefer_server_ciphers on;&#13;
}&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/29/" rel="alternate"/>
  </entry>
  <entry>
    <id>28</id>
    <title>[Python]Python中的asyncio简单介绍</title>
    <updated>2023-09-27T02:08:19.929147+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;1.协程&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;我们经常听到过进程、线程可能很少有听到协程这个概念,但是相信大家肯定都听说过携程,但此协程非彼携程,下图可以比较好说明进程、线程、协程三者之间的关系。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="协程" class="d-block img-fluid mx-auto pic" src="/backend/files/6157image-20210202111401909.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;那么协程相对于线程有哪些优势呢?&lt;/p&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;我们都知道不管线程还是进程之间的切换调度都需要使用系统的资源,而协程的切换不是属于多线程或者多进程,而是在单个线程中进行切换的,相当于是子程序的切换,因此相对多线程数量越多的应用,协程的优势就更加明显了;&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;在多线程中如果我们需要在不同的线程中控制同一个变量,我们就需要用到锁的机制,但是协程是在同一个线程中执行的,因此不会出现同时写变量的冲突了,不需要对共享资源加锁,只需要判断执行状态,效率肯定是优于多线程的执行方式的;&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;举一个简单的消费者生产者的例子,传统的方式是两个线程一个线程写消息一个线程读取消息,通过锁机制控制队列和等待,但是如果程序猿的水平不过关很容易出现死锁的情况。如果使用协程的方式呢?Python中有一个yield的关键字(这个关键字Lua语言中也有),通过该关键字可以简单的模拟一下协程,下面的程序&amp;quot;抄袭&amp;quot;的&lt;a href="https://www.liaoxuefeng.com/wiki/1016959663602400/1017968846697824"&gt;廖雪峰网站的示例&lt;/a&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;def consumer():&#13;
    r = ''&#13;
    while True:&#13;
        n = yield r&#13;
        if not n:&#13;
            return&#13;
        print('[CONSUMER] Consuming {}'.format(n))&#13;
        r = '200 OK'&#13;
&#13;
&#13;
def produce(c):&#13;
    c.send(None)&#13;
    n = 0&#13;
    while n &amp;lt; 5:&#13;
        n = n + 1&#13;
        print('[PRODUCER] Producing {}...'.format(n))&#13;
        r = c.send(n)&#13;
        print('[PRODUCER] Consumer return:{}'.format(r))&#13;
    c.close()&#13;
&#13;
&#13;
con = consumer()&#13;
produce(con)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;程序的执行结果如下&lt;/p&gt;&#13;
&#13;
&lt;div class="m-block-text" style="background:#eeeeee; border:1px solid #cccccc; padding:5px 10px"&gt;&#13;
&lt;p&gt;[PRODUCER] Producing 1...&lt;br /&gt;&#13;
[CONSUMER] Consuming 1&lt;br /&gt;&#13;
[PRODUCER] Consumer return:200 OK&lt;br /&gt;&#13;
[PRODUCER] Producing 2...&lt;br /&gt;&#13;
[CONSUMER] Consuming 2&lt;br /&gt;&#13;
[PRODUCER] Consumer return:200 OK&lt;br /&gt;&#13;
[PRODUCER] Producing 3...&lt;br /&gt;&#13;
[CONSUMER] Consuming 3&lt;br /&gt;&#13;
[PRODUCER] Consumer return:200 OK&lt;br /&gt;&#13;
[PRODUCER] Producing 4...&lt;br /&gt;&#13;
[CONSUMER] Consuming 4&lt;br /&gt;&#13;
[PRODUCER] Consumer return:200 OK&lt;br /&gt;&#13;
[PRODUCER] Producing 5...&lt;br /&gt;&#13;
[CONSUMER] Consuming 5&lt;br /&gt;&#13;
[PRODUCER] Consumer return:200 OK&lt;/p&gt;&#13;
&lt;/div&gt;&#13;
&#13;
&lt;p&gt;在函数&lt;code&gt;consumer&lt;/code&gt;中使用了yield关键字,所以该函数不是一个函数了而是一个生成器,然后将其传入到&lt;code&gt;produce&lt;/code&gt;函数中&lt;/p&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;首先调用&lt;code&gt;send()&lt;/code&gt;函数启动生成器;&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;如果&lt;code&gt;produce&lt;/code&gt;中生产了东西,则通过&lt;code&gt;c.send(n)&lt;/code&gt;切换到生成器&lt;code&gt;consumer&lt;/code&gt;中执行;&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;&lt;code&gt;consumer&lt;/code&gt;通过&lt;code&gt;yield&lt;/code&gt;拿到消息,又通过&lt;code&gt;yield&lt;/code&gt;返回结果;&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;&lt;code&gt;produce&lt;/code&gt;拿到&lt;code&gt;consumer&lt;/code&gt;处理的结果,继续生产下一条消息;&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;&lt;code&gt;produce&lt;/code&gt;决定不生产了,通过&lt;code&gt;c.close()&lt;/code&gt;关闭&lt;code&gt;consumer&lt;/code&gt;,整个生产消费过程&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&#13;
&lt;h1&gt;2.asyncio&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;asyncio是Python中用来编写并发代码的库。使用 &lt;strong&gt;async/await&lt;/strong&gt; 语法。asyncio 被用作多个提供高性能 Python 异步框架的基础,包括网络和网站服务,数据库连接库,分布式任务队列等等。asyncio 往往是构建 IO 密集型和高层级 &lt;strong&gt;结构化&lt;/strong&gt; 网络代码的最佳选择。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;3.简单的例子&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;要真正运行一个协程,asyncio 提供了三种主要机制:&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;&lt;a href="https://docs.python.org/zh-cn/3/library/asyncio-task.html#asyncio.run"&gt;&lt;code&gt;asyncio.run()&lt;/code&gt;&lt;/a&gt; 函数用来运行最高层级的入口点 &amp;quot;main()&amp;quot; 函数 (参见上面的示例。)&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;等待一个协程。以下代码段会在等待 1 秒后打印 &amp;quot;hello&amp;quot;,然后 &lt;em&gt;再次&lt;/em&gt; 等待 2 秒后打印 &amp;quot;world&amp;quot;:&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;h2&gt;a.运行一个协程&lt;/h2&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;import asyncio&#13;
import time&#13;
&#13;
async def say_after(delay, what):&#13;
    await asyncio.sleep(delay)&#13;
    print(what)&#13;
&#13;
async def main():&#13;
    print(f"started at {time.strftime('%X')}")&#13;
&#13;
    await say_after(1, 'hello')&#13;
    await say_after(2, 'world')&#13;
&#13;
    print(f"finished at {time.strftime('%X')}")&#13;
&#13;
asyncio.run(main())&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;div class="m-block-text" style="background:#eeeeee; border:1px solid #cccccc; padding:5px 10px"&gt;started at 13:48:14&lt;br /&gt;&#13;
hello world&lt;br /&gt;&#13;
finished at 13:48:17&lt;/div&gt;&#13;
&#13;
&lt;h2&gt;b.并发执行&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;在上面的代码中我们并没有看到两个函数调用并发执行的效果,要是实现并发执行的效果我们需要用到&lt;code&gt;asyncio.create_task()&lt;/code&gt;函数,修改上面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;import asyncio&#13;
import time&#13;
&#13;
async def say_after(delay, what):&#13;
        await asyncio.sleep(delay)&#13;
        print(what)&#13;
&#13;
&#13;
async def main():&#13;
        t1 = asyncio.create_task(say_after(1, 'hello'))&#13;
        t2 = asyncio.create_task(say_after(2, 'world'))&#13;
        print(f"started at {time.strftime('%X')}")&#13;
        await t1&#13;
        await t2&#13;
        print(f"finished at {time.strftime('%X')}")&#13;
&#13;
asyncio.run(main())&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;div class="m-block-text" style="background:#eeeeee; border:1px solid #cccccc; padding:5px 10px"&gt;started at 13:57:33&lt;br /&gt;&#13;
hello world&lt;br /&gt;&#13;
finished at 13:57:35&lt;/div&gt;&#13;
&#13;
&lt;p&gt;可以看到运行两个协程只花费了2s的时间,完成了协程并发执行的功能。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;c.可等待对象&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;在Python中如果一个对象可以在await语句中使用,那么这个对象就是可等待对象,可等待对象主要有三种类型:&lt;code&gt;协程&lt;/code&gt;、&lt;code&gt;任务&lt;/code&gt;和&lt;code&gt;Future&lt;/code&gt;&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;协程&lt;/p&gt;&#13;
&#13;
	&lt;ol&gt;&#13;
		&lt;li&gt;&#13;
		&lt;p&gt;协程函数:通过async def形式定义的函数;&lt;/p&gt;&#13;
		&lt;/li&gt;&#13;
		&lt;li&gt;&#13;
		&lt;p&gt;协程对象:调用协程函数所返回的对象;&lt;/p&gt;&#13;
		&lt;/li&gt;&#13;
	&lt;/ol&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;task&lt;/p&gt;&#13;
&#13;
	&lt;p&gt;通过task我们可以并发执行协程,当一个协程通过asyncio.create_task()函数打包成一个任务,该协程将会自动进入schedule中准备立即运行&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;h2&gt;d.休眠&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;通过&lt;code&gt;asyncio.sleep&lt;/code&gt;函数可以阻塞指定的秒数,下面的程序每隔一秒输出当前时间&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;import asyncio&#13;
import datetime&#13;
&#13;
async def display_date():&#13;
    loop = asyncio.get_running_loop()&#13;
    end_time = loop.time() + 5.0&#13;
    while True:&#13;
        print(datetime.datetime.now())&#13;
        if (loop.time() + 1.0) &amp;gt;= end_time:&#13;
            break&#13;
        await asyncio.sleep(1)&#13;
&#13;
asyncio.run(display_date())&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;div class="m-block-text" style="background:#eeeeee; border:1px solid #cccccc; padding:5px 10px"&gt;2021-02-02 14:20:52.537776&lt;br /&gt;&#13;
2021-02-02 14:20:53.539077&lt;br /&gt;&#13;
2021-02-02 14:20:54.540297&lt;br /&gt;&#13;
2021-02-02 14:20:55.541478&lt;br /&gt;&#13;
2021-02-02 14:20:56.542681&lt;/div&gt;&#13;
&#13;
&lt;h2&gt;e.并发运行任务2&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;通过&lt;code&gt;asyncio.gather(*aws, loop=None, return_exceptions=False)&lt;/code&gt;函数可以并发执行asw序列中的可等待对象。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;import asyncio&#13;
&#13;
async def factorial(name, number):&#13;
    f = 1&#13;
    for i in range(2, number + 1):&#13;
        print(f"Task {name}: Compute factorial({i})...")&#13;
        await asyncio.sleep(1)&#13;
        f *= i&#13;
    print(f"Task {name}: factorial({number}) = {f}")&#13;
&#13;
async def main():&#13;
    # Schedule three calls *concurrently*:&#13;
    await asyncio.gather(&#13;
        factorial("A", 2),&#13;
        factorial("B", 3),&#13;
        factorial("C", 4),&#13;
    )&#13;
&#13;
asyncio.run(main())&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;div class="m-block-text" style="background:#eeeeee; border:1px solid #cccccc; padding:5px 10px"&gt;Task A is runnning at 14:59:22&lt;br /&gt;&#13;
Task B is runnning at 14:59:22&lt;br /&gt;&#13;
Task C is runnning at 14:59:22&lt;br /&gt;&#13;
Task A is finished at 14:59:23&lt;br /&gt;&#13;
Task C is finished at 14:59:23&lt;/div&gt;&#13;
&#13;
&lt;p&gt;将函数中的关键字参数&lt;code&gt;return_exceptions&lt;/code&gt;设置为False(默认),所引发的首个异常会立即传播给等待 &lt;code&gt;gather()&lt;/code&gt; 的任务。&lt;em&gt;aws&lt;/em&gt; 序列中的其他可等待对象 &lt;strong&gt;不会被取消&lt;/strong&gt; 并将继续运行。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;import asyncio&#13;
import time &#13;
&#13;
async def divid(name, number):&#13;
	print(f"Task {name} is runnning at {time.strftime('%X')}")&#13;
	ret = 12 / number&#13;
	await asyncio.sleep(1)&#13;
	print(f"Task {name} is finished at {time.strftime('%X')}")&#13;
&#13;
&#13;
async def main():&#13;
	await asyncio.gather(&#13;
		divid('A', 3),&#13;
		divid('B', 0),&#13;
		divid('C', 3)&#13;
	)&#13;
&#13;
asyncio.run(main())&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;div class="m-block-text" style="background:#eeeeee; border:1px solid #cccccc; padding:5px 10px"&gt;Task A is runnning at 15:11:55&lt;br /&gt;&#13;
Task B is runnning at 15:11:55&lt;br /&gt;&#13;
Task C is runnning at 15:11:55&lt;br /&gt;&#13;
Traceback (most recent call last):&lt;br /&gt;&#13;
&amp;nbsp; File &amp;quot;test7.py&amp;quot;, line 18, in &amp;lt;module&amp;gt;&lt;br /&gt;&#13;
&amp;nbsp; &amp;nbsp; asyncio.run(main())&lt;br /&gt;&#13;
&amp;nbsp; File &amp;quot;/usr/local/lib/python3.7/asyncio/runners.py&amp;quot;, line 43, in run&lt;br /&gt;&#13;
&amp;nbsp; &amp;nbsp; return loop.run_until_complete(main)&lt;br /&gt;&#13;
&amp;nbsp; File &amp;quot;/usr/local/lib/python3.7/asyncio/base_events.py&amp;quot;, line 587, in run_until_complete&lt;br /&gt;&#13;
&amp;nbsp; &amp;nbsp; return future.result()&lt;br /&gt;&#13;
&amp;nbsp; File &amp;quot;test7.py&amp;quot;, line 15, in main&lt;br /&gt;&#13;
&amp;nbsp; &amp;nbsp; divid(&amp;#39;C&amp;#39;, 3),&lt;br /&gt;&#13;
&amp;nbsp; File &amp;quot;test7.py&amp;quot;, line 6, in divid&lt;br /&gt;&#13;
&amp;nbsp; &amp;nbsp; ret = 12 / number&lt;br /&gt;&#13;
ZeroDivisionError: division by zero&lt;/div&gt;&#13;
&#13;
&lt;p&gt;关于asyncio暂时介绍这些东西,因为我也是在摸索阶段,如有不正确的地方欢迎指正。&amp;nbsp;&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/28/" rel="alternate"/>
  </entry>
  <entry>
    <id>27</id>
    <title>[系列教程]使用Flask搭建一个校园论坛6-论坛首页</title>
    <updated>2023-09-27T02:08:19.929118+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;1.功能简介&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在第二节中,我们已经把首页的基础框架建好了,首页分为主体以及右边的侧边栏个两个部分。在主体中我们需要显示论坛的帖子,其中帖子又可以根据一些条件进行分类,比如今日热帖、最新的帖子以及随机推荐的帖子等等。在侧边栏中需要显示一些用户信息、发布帖子入口等其他信息。本节主要是是处理首页的主体部分内容,以及侧边栏首个栏位(主要是侧边栏下面的没想好要添加什么内容)。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;2.显示帖子列表&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;为了显示帖子列表,我们需要先在渲染主页模板文件的视图函数中获取帖子的相关数据,并传入到首页模板文件中。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;a.最新的帖子&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;打开&lt;code&gt;bbs/blueprint/frontend/index.py&lt;/code&gt;模块,添加下面的代码。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask import Blueprint, render_template, request, current_app&#13;
from bbs.models import Post, VisitStatistic&#13;
from bbs.extensions import db&#13;
from sqlalchemy.sql.expression import func&#13;
from bbs.decorators import statistic_traffic&#13;
index_bp = Blueprint('index_bp', __name__)&#13;
&#13;
&#13;
@index_bp.route('/')&#13;
@index_bp.route('/index/')&#13;
def index():&#13;
    page = request.args.get('page', 1, type=int)&#13;
    pagination = Post.query.filter_by(status_id=1).order_by(Post.update_time.desc()).\&#13;
        paginate(page, per_page=current_app.config['BBS_PER_PAGE'])&#13;
    latest = pagination.items&#13;
    return render_template('frontend/index.html', latest=latest, pagination=pagination)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在上面的代码中出现了一个新的东西&lt;code&gt;paginate&lt;/code&gt;,该方法会返回一个&lt;code&gt;Pagination&lt;/code&gt;类,这样就实现了分页操作。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;当用户向首页路由发起请求后,首先会获取请求中的参数&lt;code&gt;page&lt;/code&gt;,如果不存在则默认为1,然后通过&lt;code&gt;paginate&lt;/code&gt;函数进行分页查询,获取到&lt;code&gt;Pagination&lt;/code&gt;实例,通过该实例我们可以获取到当前页码的帖子记录,然后将结果返回到模板文件中进行渲染。打开&lt;code&gt;bbs/templates/frontend/index.html&lt;/code&gt;文件,加入下面的代码。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;{% block content %}&#13;
    {{ moment.locale(auto_detect=True) }}&#13;
    &amp;lt;main&amp;gt;&#13;
        &amp;lt;div class="container mt-2"&amp;gt;&#13;
            {% include "_flash.html" %}&#13;
            &amp;lt;ul class="nav nav-pills"&amp;gt;&#13;
                &amp;lt;li class="nav-item"&amp;gt;&#13;
                    &amp;lt;a class="nav-link active" href="{{ url_for('.index') }}"&amp;gt;最新&amp;lt;/a&amp;gt;&#13;
                &amp;lt;/li&amp;gt;&#13;
                &amp;lt;li class="nav-item "&amp;gt;&#13;
                    &amp;lt;a class="nav-link" href="{{ url_for('.hot') }}"&amp;gt;热帖&amp;lt;/a&amp;gt;&#13;
                &amp;lt;/li&amp;gt;&#13;
                &amp;lt;li class="nav-item "&amp;gt;&#13;
                    &amp;lt;a class="nav-link" href="{{ url_for('.rands') }}"&amp;gt;随机&amp;lt;/a&amp;gt;&#13;
                &amp;lt;/li&amp;gt;&#13;
            &amp;lt;/ul&amp;gt;&#13;
            &amp;lt;hr class="bg-secondary"&amp;gt;&#13;
            &amp;lt;div class="row"&amp;gt;&#13;
                &amp;lt;div class="col-md-8 mt-1"&amp;gt;&#13;
                    &amp;lt;div class="tab-content"&amp;gt;&#13;
                        &amp;lt;div id="latestPost" class="tab-pane active"&amp;gt;&#13;
                            {{ post_item(latest) }}&#13;
                            &amp;lt;div class="float-right mt-3"&amp;gt;&#13;
                                {% if tag %}&#13;
                                    {{ render_pagination(pagination) }}&#13;
                                {% endif %}&#13;
                            &amp;lt;/div&amp;gt;&#13;
                        &amp;lt;/div&amp;gt;&#13;
                    &amp;lt;/div&amp;gt;&#13;
                &amp;lt;/div&amp;gt;&#13;
                {% include "frontend/slider.html" %}&#13;
            &amp;lt;/div&amp;gt;&#13;
        &amp;lt;/div&amp;gt;&#13;
    &amp;lt;/main&amp;gt;&#13;
{% endblock %}&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在上面代码中将帖子分为了三个tab分别是最新、热帖以及随机,三个tab分别定义了三个模板文件(另外两个后续会讲),在用户访问index路由的时候默认显示的最新的帖子。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在模板文件中,我们使用了&lt;code&gt;post_item()&lt;/code&gt;宏来渲染帖子的列表。因为我们显示帖子列表的页面有几个,所以可以将代码抽离出来进行宏定义,这样就减少了冗余的代码。打开&lt;code&gt;bbs/templates/&lt;/code&gt;文件夹,新建&lt;code&gt;macro.html&lt;/code&gt;宏定义文件,添加下面的代码到文件中。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;{% macro post_item(posts) %}&#13;
    &amp;lt;ul class="list-group"&amp;gt;&#13;
        {% for post in posts %}&#13;
            &amp;lt;li class="list-group-item pl-1"&amp;gt;&#13;
                &amp;lt;div class="d-flex"&amp;gt;&#13;
                    {% if post.is_anonymous == 1 %}&#13;
                        &amp;lt;img class="avatar-50 rounded mr-2" src="{{ post.user.avatar }}" alt="{{ post.user.nickname }}"&amp;gt;&#13;
                    {% else %}&#13;
                        &amp;lt;img class="avatar-50 rounded mr-2" src="{{ url_for('static', filename='img/anonymous.jpeg') }}" alt="anonymous"&amp;gt;&#13;
                    {% endif %}&#13;
                    &amp;lt;div class="flex-grow-1"&amp;gt;&#13;
                        &amp;lt;a class="text-decoration-none" href="{{ url_for('post.read', post_id=post.id) }}"&amp;gt;{{ post.title }}&amp;lt;/a&amp;gt;&#13;
                        &amp;lt;div class="row mt-2"&amp;gt;&#13;
                            &amp;lt;div class="col"&amp;gt;&#13;
                                &amp;lt;p class="mb-0 text-muted f-12"&amp;gt;&#13;
                                &amp;lt;a class="badge badge-secondary mr-2" href="{{ url_for('post.post_cate', cate_id=post.cats.id) }}"&amp;gt;{{ post.cats.name }}&amp;lt;/a&amp;gt;&#13;
                                    &amp;lt;i class="fa fa-user-o mr-1"&amp;gt;&amp;lt;/i&amp;gt;&#13;
                                    {% if post.is_anonymous == 1 %}&#13;
                                        &amp;lt;a class="text-decoration-none" href="{{ url_for('profile.index', user_id=post.author_id) }}"&amp;gt;{{post.user.nickname}}&amp;lt;/a&amp;gt;&#13;
                                    {% else %}&#13;
                                        匿名&#13;
                                    {% endif %}&#13;
                                    &amp;lt;span&amp;gt;&amp;lt;a class="text-decoration-none ml-2 text-muted flex-grow-1 text-left f-12-b" &amp;gt;&#13;
                                        &amp;lt;span data-toggle="tooltip" data-placement="right" data-timestamp="{{ post.create_time }}" data-delay="500" data-original-title="" title="{{ post.create_time }}"&amp;gt;{{ moment(post.create_time, local=True).fromNow(refresh=True) }}&amp;lt;/span&amp;gt;&#13;
                                    &amp;lt;/a&amp;gt;&amp;lt;/span&amp;gt;&amp;lt;/p&amp;gt;&#13;
                            &amp;lt;/div&amp;gt;&#13;
                            &amp;lt;div class="col text-right flex-row-reverse d-lg-flex d-none"&amp;gt;&#13;
                                &amp;lt;p class="f-12 text-muted mb-0"&amp;gt;&amp;lt;i class="fa fa-comment mr-1"&amp;gt;&amp;lt;/i&amp;gt;{{ post.comments|length }}&amp;lt;/p&amp;gt;&#13;
                                &amp;lt;p class="f-12 text-muted mb-0"&amp;gt;&amp;lt;i class="fa fa-eye mr-1"&amp;gt;&amp;lt;/i&amp;gt;{{post.read_times}} &amp;lt;/p&amp;gt;&#13;
                            &amp;lt;/div&amp;gt;&#13;
                        &amp;lt;/div&amp;gt;&#13;
                    &amp;lt;/div&amp;gt;&#13;
                &amp;lt;/div&amp;gt;&#13;
            &amp;lt;/li&amp;gt;&#13;
        {% endfor %}&#13;
    &amp;lt;/ul&amp;gt;&#13;
{% endmacro %}&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在该宏定义函数中,我们使用了bootstrap4的列表组组件来显示我们的帖子。通过for循环来遍历我们的帖子将每篇帖子的相关信息以列表的形式展示出来。同时如果帖子是用户匿名进行发布的,那么在显示帖子基本信息的时候都会用匿名信息进行填写。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;我们通过上一节的帖子发布页面发布几个测试帖子,再次访问首页就可以看到如下所示的页面了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/6730image.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;b.最热的帖子&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;同样的在&lt;code&gt;bbs/blueprint/frontend/index.py&lt;/code&gt;模块中加入下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@index_bp.route('/hot-post/')&#13;
def hot():&#13;
    page = request.args.get('page', 1, type=int)&#13;
    pagination = Post.query.order_by(Post.read_times.desc()).paginate(page, per_page=current_app.config['BBS_PER_PAGE'])&#13;
    hots = pagination.items&#13;
    tag = pagination.total &amp;gt; current_app.config['BBS_PER_PAGE']&#13;
    return render_template('frontend/index/hot-post.html', hots=hots, pagination=pagination, tag=tag)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上面的代码跟&lt;code&gt;index视图函数&lt;/code&gt;的代码十分类似,只是在数据库的查询条件上做了一些变化,通过帖子的阅读次数来进行排序,并将输出的内容进行分页处理。因此模板文件也与&lt;code&gt;index.html&lt;/code&gt;文件类似,在&lt;code&gt;bbs/templates/frontend/index&lt;/code&gt;目录中新建&lt;code&gt;hot-post.html&lt;/code&gt;模板文件,添加下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;{% extends "frontend/base.html" %}&#13;
{% from "macro.html" import post_item, render_pagination with context%}&#13;
{% block title %}&#13;
    主页&#13;
{% endblock %}&#13;
{% block content %}&#13;
    {{ moment.locale(auto_detect=True) }}&#13;
    &amp;lt;main&amp;gt;&#13;
        &amp;lt;div class="container mt-2"&amp;gt;&#13;
            {% include "_flash.html" %}&#13;
            &amp;lt;ul class="nav nav-pills"&amp;gt;&#13;
                &amp;lt;li class="nav-item"&amp;gt;&#13;
                    &amp;lt;a class="nav-link" href="{{ url_for('.index') }}"&amp;gt;最新&amp;lt;/a&amp;gt;&#13;
                &amp;lt;/li&amp;gt;&#13;
                &amp;lt;li class="nav-item"&amp;gt;&#13;
                    &amp;lt;a class="nav-link active" href="{{ url_for('.hot') }}"&amp;gt;热帖&amp;lt;/a&amp;gt;&#13;
                &amp;lt;/li&amp;gt;&#13;
                &amp;lt;li class="nav-item "&amp;gt;&#13;
                    &amp;lt;a class="nav-link" href="{{ url_for('.rands') }}"&amp;gt;随机&amp;lt;/a&amp;gt;&#13;
                &amp;lt;/li&amp;gt;&#13;
            &amp;lt;/ul&amp;gt;&#13;
            &amp;lt;hr class="bg-secondary"&amp;gt;&#13;
            &amp;lt;div class="row"&amp;gt;&#13;
                &amp;lt;div class="col-md-8 mt-1"&amp;gt;&#13;
                    &amp;lt;div class="tab-content"&amp;gt;&#13;
                        &amp;lt;div id="latestPost" class="tab-pane active"&amp;gt;&#13;
                            {{ post_item(hots) }}&#13;
                            &amp;lt;div class="float-right mt-3"&amp;gt;&#13;
                                {% if tag %}&#13;
                                    {{ render_pagination(pagination) }}&#13;
                                {% endif %}&#13;
                            &amp;lt;/div&amp;gt;&#13;
                        &amp;lt;/div&amp;gt;&#13;
                    &amp;lt;/div&amp;gt;&#13;
                &amp;lt;/div&amp;gt;&#13;
                {% include "frontend/slider.html" %}&#13;
            &amp;lt;/div&amp;gt;&#13;
        &amp;lt;/div&amp;gt;&#13;
    &amp;lt;/main&amp;gt;&#13;
{% endblock %}&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h2&gt;c.随机推荐的帖子&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;处理完上面两个tab之后只剩下最后一个随机推荐的tab了,随机推荐的帖子数量我这里设置的是20篇帖子,我们可以使用&lt;code&gt;flask-sqlachemy&lt;/code&gt; orm框架中&lt;code&gt;func&lt;/code&gt;来轻松实现在数据库中随机获取对应条数的数据,在&lt;code&gt;bbs/blueprint/index.py&lt;/code&gt;模块中加入下面的视图函数代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from sqlalchemy.sql.expression import func&#13;
&#13;
@index_bp.route('/rand-post/')&#13;
def rands():&#13;
    rand = Post.query.filter_by(status_id=1).order_by(func.random()).limit(20)&#13;
    return render_template('frontend/index/rand-post.html', rands=rand)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;打开&lt;code&gt;bbs/templates/frontend/index&lt;/code&gt;目录中新建&lt;code&gt;rand-post.html&lt;/code&gt;模板文件,模板文件中需要添加代码与上面两小节的代码类似,这里就不在累述了,如果读者不清楚可以去项目的&lt;a href="https://github.com/weijiang1994/university-bbs"&gt;github仓库&lt;/a&gt;克隆完整的代码查看。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;3.首页侧边栏&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在上面的小节中,处理完了首页主要内容中的帖子列表显示,到此侧边栏还没有对应的内容。关于侧边栏的设计,根据不同的功能来将每个功能块用一个&lt;code&gt;card&lt;/code&gt;来显示,在这一章节的教程中只完成用户个人资料card。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;侧边栏slider是一个通用部分的内容,因此我们可以将其抽离出来,在需要使用的时候可以通过&lt;code&gt;include&lt;/code&gt;来导入,类似于我们的&lt;code&gt;base.html&lt;/code&gt;文件,打开&lt;code&gt;bbs/templates&lt;/code&gt;目录,新建&lt;code&gt;slider.html&lt;/code&gt;文件,代码清单如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;&amp;lt;div class="col-md-4 mt-1"&amp;gt;&#13;
    &amp;lt;div class="card border-secondary mb-2"&amp;gt;&#13;
        &amp;lt;div class="card-header"&amp;gt;&amp;lt;i class="fa fa-user mr-2"&amp;gt;&amp;lt;/i&amp;gt;用户&amp;lt;/div&amp;gt;&#13;
        &amp;lt;div class="card-body"&amp;gt;&#13;
            {% if not current_user.is_authenticated %}&#13;
                &amp;lt;div&amp;gt;&amp;lt;small class="text-danger"&amp;gt;&amp;lt;b&amp;gt;您尚未登录!&amp;lt;/b&amp;gt;&amp;lt;/small&amp;gt;请先&#13;
                    &amp;lt;a href="/auth/login/"&amp;gt;&amp;lt;span class="badge badge-info"&amp;gt;登录&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt; 或&#13;
                    &amp;lt;a href="/auth/register/"&amp;gt;&amp;lt;span class="badge badge-success"&amp;gt;注册&amp;lt;/span&amp;gt;&amp;lt;/a&amp;gt;!&#13;
                &amp;lt;/div&amp;gt;&#13;
            {% else %}&#13;
                &amp;lt;div class="d-flex"&amp;gt;&#13;
                    &amp;lt;img src="{{ current_user.avatar }}" class="rounded avatar-50"&amp;gt;&#13;
                    &amp;lt;div&amp;gt;&#13;
                        &amp;lt;p class="mb-1 ml-1 text-success"&amp;gt;&amp;lt;b&amp;gt;{{ current_user.nickname }}&amp;lt;/b&amp;gt;&amp;lt;/p&amp;gt;&#13;
                        &amp;lt;p class="mb-1 ml-1 text-muted"&amp;gt;&amp;lt;small&amp;gt;@{{ current_user.username }}&amp;lt;/small&amp;gt;&amp;lt;/p&amp;gt;&#13;
                    &amp;lt;/div&amp;gt;&#13;
                    &amp;lt;div class="d-flex flex-row-reverse"&amp;gt;&#13;
                        &amp;lt;a href="/post/new/" class="btn btn-secondary h-75 ml-2"&amp;gt;&amp;lt;i class="fa fa-plus"&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;&#13;
                    &amp;lt;/div&amp;gt;&#13;
                &amp;lt;/div&amp;gt;&#13;
                &amp;lt;hr class="bg-secondary"&amp;gt;&#13;
                &amp;lt;div class="d-flex p-1"&amp;gt;&#13;
                    &amp;lt;div class="pr-2 dropdown"&amp;gt;&#13;
                        &amp;lt;button class="btn btn-success dropdown-toggle" type="button" id="dropdownMenuButton"&#13;
                                data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"&amp;gt;&#13;
                            主题&#13;
                        &amp;lt;/button&amp;gt;&#13;
                        &amp;lt;div class="dropdown-menu" aria-labelledby="dropdownMenuButton"&amp;gt;&#13;
                            {% for theme_name, display_name in config.BBS_THEMES.items() %}&#13;
                                &amp;lt;a class="dropdown-item"&#13;
                                   href="{{ url_for('normal.change_theme', theme_name=theme_name, next=request.full_path) }}"&amp;gt;&#13;
                                    {{ theme_name }}&amp;lt;/a&amp;gt;&#13;
                            {% endfor %}&#13;
                        &amp;lt;/div&amp;gt;&#13;
                    &amp;lt;/div&amp;gt;&#13;
                    &amp;lt;div class="dropdown"&amp;gt;&#13;
                        &amp;lt;button class="btn btn-success dropdown-toggle" type="button" id="dropdownMenuButton"&#13;
                                data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"&amp;gt;&#13;
                            操作&#13;
                        &amp;lt;/button&amp;gt;&#13;
                        &amp;lt;div class="dropdown-menu" aria-labelledby="dropdownMenuButton"&amp;gt;&#13;
                            &amp;lt;a class="dropdown-item" href="{{url_for('profile.index', user_id=current_user.id)}}"&amp;gt;&amp;lt;i class="fa fa-fw fa-user mr-2"&amp;gt;&amp;lt;/i&amp;gt;个人中心&amp;lt;/a&amp;gt;&#13;
                            &amp;lt;a class="dropdown-item" href="{{url_for('user.index', user_id=current_user.id)}}"&amp;gt;&amp;lt;i class="fa fa-fw fa-cog mr-2"&amp;gt;&amp;lt;/i&amp;gt;设置&amp;lt;/a&amp;gt;&#13;
                            {% if current_user.role_id == 1 %}&#13;
                                &amp;lt;a class="dropdown-item" href="{{url_for('be_index.index')}}"&amp;gt;&amp;lt;i class="fa fa-fw fa-magnet mr-2"&amp;gt;&amp;lt;/i&amp;gt;后台管理&amp;lt;/a&amp;gt;&#13;
                            {% endif %}&#13;
                            &amp;lt;a class="dropdown-item" href="/auth/logout/"&amp;gt;&amp;lt;i class="fa fa-fw fa-sign-out mr-2"&amp;gt;&amp;lt;/i&amp;gt;登出&amp;lt;/a&amp;gt;&#13;
                        &amp;lt;/div&amp;gt;&#13;
                    &amp;lt;/div&amp;gt;&#13;
                &amp;lt;/div&amp;gt;&#13;
            {% endif %}&#13;
        &amp;lt;/div&amp;gt;&#13;
    &amp;lt;/div&amp;gt;&#13;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;首先通过&lt;code&gt;flask-login&lt;/code&gt;的&lt;code&gt;current_user&lt;/code&gt;属性来判断用户是否已登录,如果没有登录则显示用户登录或者注册的视图。如果用户已经登录,则显示用户已经登录的视图。在已登录视图中,做了一个权限判断如果是普通用户则不显示后台管理的URL,如果是管理员则显示。在这个视图中用户可以做一些操作,如果切换页面主题、进入个人中心、设置个人资料、退出等等,这时候我们再访问首页,效果如下所示。&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;这里有很多后端endpoint还没有实现,如果读者出现报错,可以把href这个属性全部删除掉!&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="首页效果" class="d-block img-fluid mx-auto pic" src="/backend/files/7446%E9%A6%96%E9%A1%B5%E6%95%88%E6%9E%9C.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;这一章节的内容就大致说完了,读者可以到项目&lt;a href="https://github.com/weijiang1994/university-bbs"&gt;github仓库&lt;/a&gt;下载完整的示例代码。目前代码的前端部分基本上已经完成了。&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/27/" rel="alternate"/>
  </entry>
  <entry>
    <id>26</id>
    <title>[系列教程]使用Flask搭建一个校园论坛5-帖子发布</title>
    <updated>2023-09-27T02:08:19.929089+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;1.知识预览&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在本节中将会学习到以下的内容&lt;/p&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;sqlachemy的分页查询方法&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;flask-ckeditor富文本编辑器的使用&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;bootstrap 分页渲染&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&#13;
&lt;h1&gt;2.数据库模型&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;作为一个论坛的应用程序,那么帖子是必不可少的东西。因此在实现首页功能之前,需要先在数据库中建立对应表,来存储这些数据。打开`bbs/models.py模块,嵌入Post模型类&amp;nbsp;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class Post(db.Model):&#13;
     __tablename__ = 't_post'&#13;
 ​&#13;
     id = db.Column(db.INTEGER, primary_key=True, autoincrement=True, index=True)&#13;
     title = db.Column(db.String(100), index=True, nullable=False)&#13;
     content = db.Column(db.TEXT, nullable=False)&#13;
     textplain = db.Column(db.TEXT, nullable=False)&#13;
     create_time = db.Column(db.DateTime, default=datetime.datetime.now)&#13;
     update_time = db.Column(db.DateTime, default=datetime.datetime.now)&#13;
     is_anonymous = db.Column(db.INTEGER, default=1, comment='post is anonymous? 2: yes 1: no')&#13;
     read_times = db.Column(db.INTEGER, default=0)&#13;
     # 用户对帖子的操作&#13;
     likes = db.Column(db.INTEGER, default=0, comment='like post persons')&#13;
     unlikes = db.Column(db.INTEGER, default=0, comment='unlike post persons')&#13;
     collects = db.Column(db.INTEGER, default=0, comment='collect post persons')&#13;
     &#13;
     # 外键id&#13;
     cate_id = db.Column(db.INTEGER, db.ForeignKey('t_postcate.id'))&#13;
     author_id = db.Column(db.INTEGER, db.ForeignKey('t_user.id'))&#13;
     status_id = db.Column(db.INTEGER, db.ForeignKey('t_status.id'), default=1)&#13;
     &#13;
     # 用户关系&#13;
     cats = db.relationship('PostCategory', back_populates='post')&#13;
     user = db.relationship('User', back_populates='post')&#13;
     status = db.relationship('Status', back_populates='post')&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在上面的代码中,定义了&lt;code&gt;Post&lt;/code&gt;类的相关属性。论坛里面的帖子数量庞大,且种类也十分多,因此使用了外键&lt;code&gt;cate_id&lt;/code&gt;来关联帖子的类别,同时使用&lt;code&gt;author_id&lt;/code&gt;来关联当前帖子属于哪个用户发布。用户发布了帖子之后当然也可以删除帖子,使用&lt;code&gt;status_id&lt;/code&gt;来关联帖子的状态。同时当帖子被举报次数较多时候,论坛管理员也可以屏蔽帖子,因此通过&lt;code&gt;status_id&lt;/code&gt;可以来定义帖子的不同状态。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在前面的章节中,已经建好了user、status表的相关信息,因此这里只需要创建Category模型类即可,同时需要在status模型中将post的外键关系加进去,继续在该模块中加入下面的代码。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class PostCategory(db.Model):&#13;
     __tablename__ = 't_postcate'&#13;
 ​&#13;
     id = db.Column(db.INTEGER, primary_key=True, autoincrement=True)&#13;
     name = db.Column(db.String(40), nullable=False)&#13;
     create_time = db.Column(db.Date, default=datetime.date.today)&#13;
 ​&#13;
     post = db.relationship('Post', back_populates='cats', cascade='all')&#13;
     &#13;
class Status(db.Model):&#13;
     # 省略已有的代码&#13;
     post = db.relationship('Post', back_populates='status', cascade='all')&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;到此为止,目前关于帖子相关的数据库表的信息就创建完成了。但是在帖子类别表中还没有数据,我们可以手动到数据库中去添加,或者在&lt;code&gt;bbs/__init__.py&lt;/code&gt;文件中添加下面的代码.&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;def register_cmd(app: Flask):&#13;
     @app.cli.command()&#13;
     def init():&#13;
         init_cate()&#13;
         click.echo('初始化帖子类别表完成!')&#13;
 ​&#13;
def init_cate():&#13;
     categories = ['杂谈', '趣事', '表白', '寻物', '咸鱼', '活动']&#13;
     for category in categories:&#13;
         pc = PostCategory(name=category)&#13;
         db.session.add(pc)&#13;
     db.session.commit()&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在init_cate()函数中定义了六种帖子类别,这里读者可以自由发挥。然后我们在终端中输入下面的命令将帖子类别数据加入到数据库中&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;flask init&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;使用该命令会清楚数据库中的原始数据!&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;h1&gt;3.flask-migrate&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;我们创建完了数据库之后,可以根据第三节的&lt;code&gt;flask init&lt;/code&gt;来初始化数据库,但是如果我们在开发环境中有很多测试数据了,如果进行数据库初始化,那将会把原来的数据都清空掉。如果我们使用的是MySQL数据库,我们可以通过&lt;code&gt;flask-migrate&lt;/code&gt;这个工具来对数据库进行升级以及迁移。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;首先在虚拟环境中安装flask-migrate&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;pip install flask-migrate&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;安装程序执行完成之后,需要在我们的程序中实例化&lt;code&gt;migrate&lt;/code&gt;。打开&lt;code&gt;bbs/extensions.py&lt;/code&gt;模型,添加下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask_migrate import Migrate&#13;
mg = Migrate()&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;然后打开&lt;code&gt;bbs/__init__.py&lt;/code&gt;模块,将实例化的migrate初始化&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from extensions import mg&#13;
def register_extensions(app: Flask):&#13;
    # 省略已有代码&#13;
    mg.init_app(app, db)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;完成上面的操作之后,在终端中输入下面的命令,创建数据库迁移环境,&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;flask db init&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;我们可以在我们项目的同级目录中看到一个&lt;code&gt;migrations&lt;/code&gt;的文件夹,该文件中保存的就是数据迁移的相关文件,使用下面的命令创建迁移文件。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;flask db migrate -m "add post table"&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在上面的命令中-m是可选的参数,加上只是为了可以&lt;code&gt;track&lt;/code&gt;某些操作,当我们迁移出错之后。然后打开&lt;code&gt;migrations&lt;/code&gt;文件夹,我们可以看到生成的迁移文件,文件内容读者可以自行去查看,然后通过下面的命令进行数据库的升级操作。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;flask db upgrade&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;在进行git上传的时候,最好是吧migrations文件夹加入.gitignore文件中去,这样我们在生产环境进行数据库迁移的时候就不会出现冲突了。&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;h3&gt;4.发布帖子&lt;/h3&gt;&#13;
&#13;
&lt;p&gt;在创建好相应的前序工作之后,我们就可以开始实现发布帖子的功能了。在实现发布帖子的功能,我们使用到了flask-ckeditor库,通过该库可以很轻松的实现富文本编辑功能,首先需要安装flask-ckeditor。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;a.flask-ckedito配置&lt;/h2&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;pip install flask-ckeditor&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;安装完成之后,使用CKEditor需要做一些初始化配置,打开bbs/settting.py模块,在BaseConfig类中添加下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class BaseConfig(object):&#13;
     # 省略已有代码&#13;
     # CKEditor configure&#13;
     CKEDITOR_SERVE_LOCAL = True&#13;
     CKEDITOR_ENABLE_CODESNIPPET = True&#13;
     CKEDITOR_HEIGHT = 400&#13;
     CKEDITOR_FILE_UPLOADER = 'normal.image_upload'&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在给&lt;code&gt;CKEditor&lt;/code&gt;配置文件上传路由时,配置了&lt;code&gt;normal.image_upload&lt;/code&gt;路由,但是此时我们的应用程序中还没有这个路由,因此需要创建这个路由,打开&lt;code&gt;bbs/blueprint/frontend/&lt;/code&gt;文件夹,创建一个名为&lt;code&gt;normal.py&lt;/code&gt;的模块,添加下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask import Blueprint&#13;
normal_bp = Blueprint('normal', __name__, url_prefix='/normal')&#13;
&#13;
&#13;
@normal_bp.route('/image/upload/')&#13;
def image_upload():&#13;
    pass&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;之后需要把该蓝图在&lt;code&gt;__init__.py&lt;/code&gt;文件中进行注册,在这里我们先不对文件上传视图函数做处理,后面我们再来处理这个函数。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;b.创建表单&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;同样使用wtfform来渲染前端创建帖子的表单,打开&lt;code&gt;bbs/forms.py&lt;/code&gt;模块,添加下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class BasePostForm(FlaskForm):&#13;
     title = StringField(u'标题', validators=[DataRequired(message='帖子标题不能为空'),&#13;
                                            Length(min=1, max=50, message='用户名长度必须在1到50位之间')],&#13;
                         render_kw={'class': '', 'rows': 50, 'placeholder': '输入您的帖子标题'})&#13;
     category = SelectField(label=u'分区',&#13;
                            default=0,&#13;
                            coerce=int)&#13;
     anonymous = SelectField(label=u'是否匿名', default=1, choices=[(1, '实名'), (2, '匿名')], coerce=int)&#13;
     body = CKEditorField('帖子内容', validators=[DataRequired(message='请输入帖子内容')])&#13;
     submit = SubmitField(u'发布', render_kw={'class': 'source-button btn btn-primary btn-xs mt-2 text-right'})&#13;
 ​&#13;
     def __init__(self, *args, **kwargs):&#13;
         super(BasePostForm, self).__init__(*args, **kwargs)&#13;
         categories = PostCategory.query.all()&#13;
         self.category.choices = [(cate.id, cate.name) for cate in categories]&#13;
 ​&#13;
 ​&#13;
# noinspection PyMethodMayBeStatic&#13;
class CreatePostForm(BasePostForm):&#13;
 ​&#13;
     def validate_title(self, filed):&#13;
         if Post.query.filter_by(title=filed.data).first():&#13;
             raise ValidationError('该标题已存在请换一个!')&#13;
 ​&#13;
 ​&#13;
class EditPostForm(BasePostForm):&#13;
     submit = SubmitField(u'保存编辑', render_kw={'class': 'source-button btn btn-danger btn-xs mt-2 text-right'})&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;跟用户登录、注册表单类似,因为帖子除了发布之外还可以编辑,因此可以通过基类定义共同属性,在子类中定义各自的特有属性。在基类中我们定义了表单的字段,同时通过构造函数去获取了&lt;code&gt;PostCategory&lt;/code&gt;中所有类别,并将其值赋给&lt;code&gt;category&lt;/code&gt;字段了。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;c.后端逻辑&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;在&lt;code&gt;bbs/blueprint/frontend/&lt;/code&gt;中创建名为&lt;code&gt;post.py&lt;/code&gt;的模块,添加下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;import datetime&#13;
 ​&#13;
from flask import Blueprint, render_template, flash, redirect, url_for, request, jsonify, current_app&#13;
 ​&#13;
from bbs.blueprint.frontend.normal import to_html&#13;
from bbs.models import Post, Collect, PostReport, ReportCate, Comments, Notification, CommentStatistic, PostStatistic, \&#13;
     PostCategory&#13;
from bbs.forms import CreatePostForm, EditPostForm&#13;
from flask_login import login_required, current_user&#13;
from bbs.extensions import db&#13;
from bbs.utils import get_text_plain, EMOJI_INFOS&#13;
from bbs.decorators import statistic_traffic&#13;
 ​&#13;
post_bp = Blueprint('post', __name__, url_prefix='/post')&#13;
 ​&#13;
 ​&#13;
@post_bp.route('/new/', methods=['GET', 'POST'])&#13;
@login_required&#13;
def new_post():&#13;
     form = CreatePostForm()&#13;
     if form.validate_on_submit():&#13;
         title = form.title.data&#13;
         cate = form.category.data&#13;
         anonymous = form.anonymous.data&#13;
         content = form.body.data&#13;
         textplain = get_text_plain(content)&#13;
         post = Post(title=title, cate_id=cate, content=content, is_anonymous=anonymous, author_id=current_user.id,&#13;
                     textplain=textplain)&#13;
         db.session.add(post)&#13;
         db.session.commit()&#13;
         flash('帖子发布成功!', 'success')&#13;
         return redirect(url_for('post.read', post_id=post.id))&#13;
     return render_template('frontend/post/new-post.html', form=form)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在该模块中,前端用户所有关于帖子的操作都会存放于此,因此在实例化蓝图的时候添加了&lt;code&gt;url_prefix&lt;/code&gt;位置参数,此蓝图中的所有视图函数都会有&lt;code&gt;post&lt;/code&gt;的前缀url,在该蓝图中的所有视图函数都是用于帖子的相关操作。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;与此同时,我们添加了一个new_post()视图函数,在视图函数上面添加了两个装饰器&lt;/p&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;post_bp.rout()&lt;/p&gt;&#13;
&#13;
	&lt;p&gt;路由装饰器,这里就不做过多的解释了,属于flask的最基本知识点&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;login_required&lt;/p&gt;&#13;
&#13;
	&lt;p&gt;这个装饰器是用来判断限定只有用户登录之后才能进行发布帖子的操作,属于&lt;code&gt;flask_login&lt;/code&gt;第三方模块的装饰器。其原理就是通过session来判断用户是否登录,如果没有则跳转到系统定义的登录页面;&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&#13;
&lt;p&gt;之后实例化了上一步创建的表单类,通过&lt;code&gt;validate_form_submit&lt;/code&gt;来判断用户是提交表单数据还是初始进入创建帖子页面,如果是初始进入页面则返回模板文件渲染,如果是提交则将用户提交的信息添加到数据库中,实现发布帖子的功能。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;现在我们还没有创建对应的模板文件,接下来就需要创建发布帖子的模板文件。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;d.发布帖子模板文件&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;打开&lt;code&gt;bbs/templates/frontend/post/&lt;/code&gt;文件夹,新建&lt;code&gt;new-post.html&lt;/code&gt;文件,在文件中添加如下代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;{% extends "frontend/base.html" %}&#13;
 {% import 'bootstrap/wtf.html' as wtf %}&#13;
 {% block title %}&#13;
     发布新的帖子&#13;
 {% endblock %}&#13;
 {% block content %}&#13;
     &amp;lt;body&amp;gt;&#13;
     &amp;lt;main&amp;gt;&#13;
         &amp;lt;div class="container mt-3"&amp;gt;&#13;
             &amp;lt;h3 class="text-info"&amp;gt;&amp;lt;strong&amp;gt;新的帖子&amp;lt;/strong&amp;gt;&amp;lt;/h3&amp;gt;&#13;
             &amp;lt;hr class="bg-secondary"&amp;gt;&#13;
             &amp;lt;form action="/post/new/" method="post"&amp;gt;&#13;
                 {{ form.csrf_token }}&#13;
                 {{ wtf.form_field(form.title) }}&#13;
                 &amp;lt;div class="row"&amp;gt;&#13;
                     &amp;lt;div class="col"&amp;gt;&#13;
                         {{ wtf.form_field(form.category) }}&#13;
                     &amp;lt;/div&amp;gt;&#13;
                     &amp;lt;div class="col"&amp;gt;&#13;
                         {{ wtf.form_field(form.anonymous) }}&#13;
                     &amp;lt;/div&amp;gt;&#13;
                 &amp;lt;/div&amp;gt;&#13;
                 {{ form.body }}&#13;
                 &amp;lt;div class="text-right"&amp;gt;&#13;
                     {{ form.submit }}&#13;
                 &amp;lt;/div&amp;gt;&#13;
             &amp;lt;/form&amp;gt;&#13;
         &amp;lt;/div&amp;gt;&#13;
     &amp;lt;/main&amp;gt;&#13;
     &amp;lt;/body&amp;gt;&#13;
     {{ ckeditor.load() }}&#13;
     {{ ckeditor.config(name='body') }}&#13;
 {% endblock %}&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;同样的还是通过继承基模板来实现我们的页眉跟页脚的布局,然后在content块中定义我们自己的内容。content中的内容跟注册表单差不多,就是把后端的wtfform字段进行渲染。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;{{ ckeditor.load() }}&#13;
{{ ckeditor.config(name='body') }}&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;通过这两段代码我们就渲染了CKEditor的富文本编辑器了。尤其注意config中的name=&amp;#39;body&amp;#39;,这个body就是我们BasePostForm中定义的字段名,根据该字段名就会将CKEditor渲染到该前端元素中去。然后我们在浏览器中输入&lt;a href="http://127.0.0.1:5000/post/new/"&gt;http://127.0.0.1:5000/post/new/&lt;/a&gt;就可以看到下面的页面了,CKEditor最上边工具栏中的图片上传还不能使用,在后面的章节中将会进行处理。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="发布帖子" class="d-block img-fluid mx-auto pic" src="/backend/files/9094%E5%8F%91%E5%B8%83%E5%B8%96%E5%AD%90.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在页面中随便输入一些信息,点击发布,然后到数据库中就可以看到相关的帖子数据啦。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;好啦,感谢各位看官读到这里,本节的功能就全部完成了。在下一节中将会实现阅读帖子内容,以及首页相关内容。&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/26/" rel="alternate"/>
  </entry>
  <entry>
    <id>25</id>
    <title>[Linux]使用ssh时的一些骚操作</title>
    <updated>2023-09-27T02:08:19.929058+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;基本使用&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在Windows平台上,可以使用免费的putty工具来进行ssh连接,同时也有其他功能更加强大的收费工具,比如XShell等等。这些工具都具有可视化界面,具体使用方法这里就不再累述。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在Linux或者MacOS平台上,在终端中使用下面的命令就能很简单的连接远程主机了。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;ssh hostName@hostIP &lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h1&gt;配置ssh命令别称&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在Linux发行版或者MacOS系统中,默认的都会在系统中安装&lt;code&gt;bash&lt;/code&gt;工具,工具一般位于&lt;code&gt;/usr/bin/bash&lt;/code&gt;。通过&lt;kbd&gt;Ctrl+Shift+T&lt;/kbd&gt;组合按键可以来打开终端工具,在每次打开终端时,都会去加载&lt;code&gt;/home/username/.bashrc&lt;/code&gt;文件中的配置,查看&lt;code&gt;.bashrc&lt;/code&gt;文件中的内容如下图&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt=".bashrc" class="d-block img-fluid mx-auto pic" src="/backend/files/9972bashrc%E6%96%87%E4%BB%B6%E5%86%85%E5%AE%B9.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在上图中可以看到很多&lt;code&gt;alias&lt;/code&gt;翻译过来的意思就是别名,比如&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;alias ll=&amp;#39;ls -alF&amp;#39;&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;alias la=&amp;#39;ls -A&amp;#39;&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;p&gt;在终端中我们使用&lt;code&gt;ll&lt;/code&gt;就是执行了&lt;code&gt;ls -alF&lt;/code&gt;命令,执行&lt;code&gt;la&lt;/code&gt;就相当于执行了&lt;code&gt;ls -A&lt;/code&gt;的命令。因此也可以将我们常用的命令配置成别名的方式,使用下面的方式配置ssh别名&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo vim ~/.bashrc&#13;
# 在文件内容末尾输入下面的内容&#13;
alias stx='ssh ubuntu@hostIP  -p 22'&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;其中hostIP就是你需要连接的远程主机的公网IP地址,退出保存,执行如下命令&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;source ~/.bashrc&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;注意修改完了&lt;code&gt;~/.bashrc&lt;/code&gt;文件之后,一定要记得执行&lt;code&gt;source&lt;/code&gt;命令,不然不会生效,当然你也可以重新开启一个终端窗口。然后在终端中输入&lt;code&gt;stx&lt;/code&gt;命令就可以连接你的远程主机了,输入密码就可以进入你的远程主机了。如果你有经常需要连接的远程主机,使用该方式可以省去我们每次都需要输入远程主机名跟IP了,大大提高工作效率。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;sshpass工具&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;上面的方法确实可以节省我们输入远程主机名以及IP地址的时间,但是有一个问题是我们每次都需要输入远程主机的登录密码,那么有没有一种方式让我们省去输入密码步骤呢?答案是肯定的,我们可以通过sshpass工具将登录密码直接附加上登录命令中。首先我们通过下面的命令来安装&lt;code&gt;sshpass&lt;/code&gt;工具&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo apt-get install sshpass&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;之后可以通过下面的命令测试是否可用&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sshpass -p password ssh -p 22 hostName@hostIP&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;测试成功之后就可以按照方法一一样将该命令配置一个别名,然后在命令行中输入&lt;code&gt;sptx&lt;/code&gt;就可以免去密码直接登录远程主机了。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo vim ~/.bashrc&#13;
alias sptx='sshpass -p password ssh -p 22 hostName@hostIP'&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="sshpass" class="d-block img-fluid mx-auto pic" src="/backend/files/3364image-20210122101105986.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;SSH-key 免密登录&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;上面的方式其实算不上真正的免密登录,我们只是把登录密码使用其他工具附加到登录命令中了。那怎么真正的实现免密登录呢?我们可以通过ssh-key来实现这一需求。&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;在本机生成ssh-key&lt;/p&gt;&#13;
&#13;
	&lt;p&gt;首先查看本机是否已经生成了ssh-key。&lt;/p&gt;&#13;
&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;cd ~/.ssh&#13;
ls | grep id&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
	&lt;p&gt;如果存在,则可以省去下面这个步骤。如果不存在,则使用下面的命令生成本机秘钥。&lt;/p&gt;&#13;
&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;ssh-keygen -t rsa&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
	&lt;p&gt;一路回车就生成了本机的ssh秘钥了。其中&lt;code&gt;id_rsa&lt;/code&gt;为私钥,&lt;code&gt;id_rsa.pub&lt;/code&gt;为公钥,我们将公钥拷贝到远程主机中&lt;/p&gt;&#13;
&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;scp ~/.ssh/id_rsa.pub hostName@hoatIP&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
	&lt;p&gt;在远程主机中将客户端的公钥写入&lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt;文件中&lt;/p&gt;&#13;
&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;cat id_rsa.pub &amp;gt;&amp;gt; ~/.ssh/authorized_keys&lt;/code&gt;&lt;/pre&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;客户端配置&lt;br /&gt;&#13;
	虽然这样子配置之后可以免密登录,但是每次还是需要输入远程主机名、IP等等。可以通过客户端的&lt;code&gt;~/.ssh/config&lt;/code&gt;配置服务器的相关参数简化登录命令。&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo vim ~/.ssh/config&#13;
# 填入下面的内容在文件结尾&#13;
Host Servername&#13;
         HostName IP&#13;
         Port 22&#13;
         User hostName&#13;
         IdentityFile ~/.ssh/id_rsa&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
	&lt;p&gt;然后在终端中使用你配置的Servername就可以登录远程主机了。&lt;/p&gt;&#13;
&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;ssh Servername&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
	&lt;p&gt;当然我们也可以根据方法一配置别名使得命令更加简短,这里就不再做出说明了。&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/25/" rel="alternate"/>
  </entry>
  <entry>
    <id>24</id>
    <title>[应用部署]使用github+jsdelivr加速网站静态资源访问速度</title>
    <updated>2023-09-27T02:08:19.929019+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;1. github CDN&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;查看jsdelivr文档github CND那小节,可以看到我们可以如下内容&lt;/p&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;加载任何github仓库的release、commit、branch&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-http"&gt;https://cdn.jsdelivr.net/gh/user/repo@version/file&lt;/code&gt;&lt;/pre&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;加载指定版本的文件&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-http"&gt;https://cdn.jsdelivr.net/gh/jquery/jquery@3.2.1/dist/jquery.min.js&#13;
https://cdn.jsdelivr.net/gh/jquery/jquery@32b00373b3f42e5cdcb709df53f3b08b7184a944/dist/jquery.min.js&lt;/code&gt;&lt;/pre&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;使用范围版本代替指定版本&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-http"&gt;https://cdn.jsdelivr.net/gh/jquery/jquery@3.2/dist/jquery.min.js&#13;
https://cdn.jsdelivr.net/gh/jquery/jquery@3/dist/jquery.min.js&lt;/code&gt;&lt;/pre&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;忽略版本使用latest来加载最新的release版本(不推荐在生产环境中使用)&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-http"&gt;https://cdn.jsdelivr.net/gh/jquery/jquery@latest/dist/jquery.min.js&#13;
https://cdn.jsdelivr.net/gh/jquery/jquery/dist/jquery.min.js&lt;/code&gt;&lt;/pre&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;在任何JS / CSS文件中添加&amp;ldquo; .min&amp;rdquo;以获得缩小版本-如果不存在,我们将为您生成。 所有生成的文件都带有源映射,可以在开发期间轻松使用:&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-http"&gt;https://cdn.jsdelivr.net/gh/jquery/jquery@3.2.1/src/core.min.js&lt;/code&gt;&lt;/pre&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;获取目录清单&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-http"&gt;https://cdn.jsdelivr.net/gh/jquery/jquery@3.2.1/&#13;
https://cdn.jsdelivr.net/gh/jquery/jquery@3.2.1/dist/&lt;/code&gt;&lt;/pre&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&#13;
&lt;p&gt;有了以上的内容,我们就可以通过github来托管我们的静态资源文件,然后使用jsdelivr来加载了。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;2. github相关操作&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在进行jsdelivr加载之前,我们需要在github上进行一系列操作。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;新建github仓库&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;进入个人github 看板页面,点击New,既可以进入创建新的仓库页面,相关信息可以自定义。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="create repo" class="d-block img-fluid mx-auto pic" src="/backend/files/8345%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_39.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;克隆仓库&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;点击Create repository之后,会自动跳转到仓库主页中去,点击Code按钮,复制clone连接&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="git clone" class="d-block img-fluid mx-auto pic" src="/backend/files/1192%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20210118140029.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;进入你想存放仓库的文件夹,输入下面的命令进行仓库克隆&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;git clone 复制的连接&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h2&gt;添加静态资源文件&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;将仓库克隆到本地之后,把你想要加载的静态资源文件存放到本地仓库中,然后使用如下命令推送到github上&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code&gt;git add --al&#13;
git commit&#13;
git push&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h2&gt;生成release&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;推送完成之后,点击仓库右侧的new release按钮,新建一个release版本,信息由你自己定义,然后点击publish按钮即可。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="release" class="d-block img-fluid mx-auto pic" src="/backend/files/9808%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_40.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;3. 使用jsdelivr加载&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;完成了上述步骤之后,就可以通过jsdelivr进行加载了,在我的仓库中我存放了bootstrap4.3.1的css文件以及js文件,在需要引入的html文件中引入方式如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;&amp;lt;link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/weijiang1994/BlogCDN@1.0/bootstrap4/bootstrap.4.3.1.min.css"&amp;gt;&#13;
&amp;lt;script src="https://cdn.jsdelivr.net/gh/weijiang1994/BlogCDN@1.0/bootstrap4/bootstrap.4.3.1.min.js"&amp;gt;&amp;lt;/script&amp;gt;&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;引用的格式如下&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;https://cdn.jsdelivr.net/gh/github用户名/github仓库名@release版本号/文件路径&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;h1&gt;4.QA&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt;jsdelivr能提高加载速度吗?&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt;jsdelivr在国内有很多节点,因此裸连的速度也是相当的客观。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt;删除github仓库还能生效吗?&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt;jsdelivr使用永久性的S3存储,以确保即使GitHub发生故障,或者其作者删除了存储库或发行版,所有文件仍然可用。 仅在第一次或S3故障时才直接从GitHub提取文件。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;Q:&lt;/strong&gt;其他玩法?&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;A:&lt;/strong&gt;在你自己的服务器带宽很低的情况下,可以通过这种方式加载大文件,测试了加载12M的图片文件,仅仅耗时4s不到。&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/24/" rel="alternate"/>
  </entry>
  <entry>
    <id>23</id>
    <title>[系列教程]使用Flask搭建一个校园论坛4-登录注册</title>
    <updated>2023-09-27T02:08:19.928983+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;1.知识预览&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在本届中将学习到以下内容的知识&lt;/p&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;如何使用wtform来渲染表单&lt;/li&gt;&#13;
	&lt;li&gt;如果使用flask-mail来发送邮件&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&#13;
&lt;h1&gt;2.用户注册&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在前端中form表单是用的比较多的东西,我们可以使用&lt;code&gt;wtforms&lt;/code&gt;这个框架,直接通过后端代码来渲染前端表单。新建&lt;code&gt;&lt;strong&gt;bbs/forms.py&lt;/strong&gt;&lt;/code&gt;文件,嵌入以下代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask_wtf import FlaskForm&#13;
from wtforms import StringField, SubmitField, SelectField, BooleanField, TextAreaField, FileField, Label, HiddenField, \&#13;
    PasswordField&#13;
&#13;
&#13;
class BaseUserForm(FlaskForm):&#13;
    user_name = StringField(u'用户名',&#13;
                            validators=[DataRequired(message='用户名不能为空'),&#13;
                                        Length(min=1, max=16, message='用户名长度限定在1-16位之间'),&#13;
                                        Regexp('^[a-zA-Z0-9_]*$',&#13;
                                               message='用户名只能包含数字、字母以及下划线.')],&#13;
                            render_kw={'placeholder': '请输入用户名长度1-16之间'})&#13;
    nickname = StringField(u'昵称',&#13;
                           validators=[DataRequired(message='昵称不能为空'),&#13;
                                       Length(min=1, max=20, message='昵称长度限定在1-20位之间')],&#13;
                           render_kw={'placeholder': '请输入昵称长度1-20之间'})&#13;
    user_email = StringField(u'注册邮箱',&#13;
                             render_kw={'placeholder': '请输入注册邮箱', 'type': 'email'})&#13;
&#13;
    submit = SubmitField(u'注册', render_kw={'class': 'btn btn-success btn-xs'})&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在上面的代码中,首先导入相关的库,然后新建了一个&lt;code&gt;BaseUserForm&lt;/code&gt;的类,因为用户的信息在很多表单中使用到了,因此可以将共同的属性剥离出来,然后在不同的场合继承该基类,并且可以根据不同的场合在子类中定制我们的表单属性,这样就可以降低代码的冗余量。如果在每个需要使用到用户信息的表单代码中写入同样的内容,那么久显得代码很臃肿了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;code&gt;BaseUserForm&lt;/code&gt;类中使用到了&lt;code&gt;wtforms&lt;/code&gt;中的一些属性,比如&lt;code&gt;StringField&lt;/code&gt;就相当于是我们前端的&lt;code&gt;input&lt;/code&gt;标签,&lt;code&gt;SubmitField&lt;/code&gt;就相当于是&lt;code&gt;&amp;lt;input type=&amp;quot;submit&amp;quot;&amp;gt;&lt;/code&gt;,具体可以去看&lt;a href="https://link.zhihu.com/?target=https%3A//wtforms.readthedocs.io/en/2.3.x/" target="_blank"&gt;wtforms&lt;/a&gt;的官方文档。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;继续在上面的文件中嵌入如下代码,新建注册表单类&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class RegisterForm(FlaskForm):&#13;
    user_name = StringField(u'用户名',&#13;
                            validators=[DataRequired(message='用户名不能为空'),&#13;
                                        Length(min=1, max=16, message='用户名长度限定在1-16位之间'),&#13;
                                        Regexp('^[a-zA-Z0-9_]*$',&#13;
                                               message='用户名只能包含数字、字母以及下划线.')],&#13;
                            render_kw={'placeholder': '请输入用户名长度1-16之间'})&#13;
    nickname = StringField(u'昵称',&#13;
                           validators=[DataRequired(message='昵称不能为空'),&#13;
                                       Length(min=1, max=20, message='昵称长度限定在1-16位之间')],&#13;
                           render_kw={'placeholder': '请输入昵称长度1-20之间'})&#13;
    user_email = StringField(u'注册邮箱',&#13;
                             validators=[DataRequired(message='注册邮箱不能为空'),&#13;
                                         Length(min=4, message='注册邮箱长度必须大于4')],&#13;
                             render_kw={'placeholder': '请输入注册邮箱', 'type': 'email'})&#13;
    password = StringField(u'密码',&#13;
                           validators=[DataRequired(message='用户密码不能为空'),&#13;
                                       Length(min=8, max=40, message='用户密码长度限定在8-40位之间'),&#13;
                                       EqualTo('confirm_pwd', message='两次密码不一致')],&#13;
                           render_kw={'placeholder': '请输入密码', 'type': 'password'})&#13;
    confirm_pwd = StringField(u'确认密码',&#13;
                              validators=[DataRequired(message='用户密码不能为空'),&#13;
                                          Length(min=8, max=40, message='用户密码长度限定在8-40位之间')],&#13;
                              render_kw={'placeholder': '输入确认密码', 'type': 'password'})&#13;
    colleges = SelectField(u'学院', choices=[(1, '计算机')])&#13;
    submit = SubmitField(u'注册', render_kw={'class': 'source-button btn btn-primary btn-xs mt-2'})&#13;
&#13;
    def __init__(self, *args, **kwargs):&#13;
        super(RegisterForm, self).__init__(*args, **kwargs)&#13;
        cols = College.query.all()&#13;
        self.colleges.choices = [(col.id, col.name) for col in cols]&#13;
&#13;
    def validate_user_name(self, filed):&#13;
        if User.query.filter_by(username=filed.data).first():&#13;
            raise ValidationError('用户名已被注册.')&#13;
&#13;
    def validate_user_email(self, filed):&#13;
        if User.query.filter_by(email=filed.data.lower()).first():&#13;
            raise ValidationError('邮箱已被注册.')&#13;
&#13;
    def validate_nickname(self, filed):&#13;
        if User.query.filter_by(nickname=filed.data).first():&#13;
            raise ValidationError('昵称已被注册')&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;因为学院的选项有许多,我们可以在类的构造函数中通过数据库去获取数据库中已经存在的学院,然后将其设置到&lt;code&gt;colleges&lt;/code&gt;类属性的&lt;code&gt;choices&lt;/code&gt;值上,这样当我们打开页面渲染表单时,数据就会自动渲染到&lt;code&gt;select&lt;/code&gt;标签&lt;code&gt;option&lt;/code&gt;上去了,如下图&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://pic3.zhimg.com/80/v2-d47873c2d185b3a5dd625b9b8b4ec642_720w.jpg" style="margin:0px auto; width:250px" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;然后还新建了三个函数&lt;code&gt;validate_user_name&lt;/code&gt;、&lt;code&gt;validate_user_email&lt;/code&gt;以及&lt;code&gt;validate_nickname&lt;/code&gt;,这三个函数主要是用来判断&lt;code&gt;email、username、nickname&lt;/code&gt;三个字段的唯一性,因为在数据库建表的时候将这三个字段设置为&lt;code&gt;unique=True&lt;/code&gt;,因此在这里需要做一个唯一性的判断。&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;使用&lt;code&gt;&lt;strong&gt;wtforms&lt;/strong&gt;&lt;/code&gt;时,我们可以通过&lt;code&gt;&lt;strong&gt;validate_&lt;/strong&gt;&lt;/code&gt;加上你需要校验的属性字段名称来检验前端用户输入的数据是否符合标准。&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;表单类的编写已经完成,接下来就是整个注册逻辑的实现了。新建&lt;code&gt;&lt;strong&gt;bbs/templates/frontend/register.html&lt;/strong&gt;&lt;/code&gt;文件,嵌入以下代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;{% extends "frontend/base.html" %}&#13;
{% import 'bootstrap/wtf.html' as wtf %}&#13;
{% block title %}&#13;
    用户注册&#13;
{% endblock %}&#13;
{% block content %}&#13;
    &amp;lt;body&amp;gt;&#13;
    &amp;lt;main&amp;gt;&#13;
        &amp;lt;div class="container"&amp;gt;&#13;
&#13;
            &amp;lt;div class="jumbotron pt-5 pb-1 mt-2"&amp;gt;&#13;
                &amp;lt;div class="row"&amp;gt;&#13;
                    &amp;lt;div class="col-md-8"&amp;gt;&#13;
                        &amp;lt;h3 class="text-muted"&amp;gt;&amp;lt;b&amp;gt;欢迎注册加入狗子学院~&amp;lt;/b&amp;gt;&amp;lt;/h3&amp;gt;&#13;
                        &amp;lt;hr class="bg-primary"&amp;gt;&#13;
                        &amp;lt;p&amp;gt;&amp;lt;b&amp;gt;在这里你可以:&amp;lt;/b&amp;gt;&amp;lt;/p&amp;gt;&#13;
                        &amp;lt;ul&amp;gt;&#13;
                            &amp;lt;li&amp;gt;浏览当下校园的一些趣事、杂谈以及谁和谁的八卦&amp;lt;/li&amp;gt;&#13;
                            &amp;lt;li&amp;gt;发布一些咸鱼交易、寻物启事等等&amp;lt;/li&amp;gt;&#13;
                            &amp;lt;li&amp;gt;发现臭味相投的朋友、开拓自己的圈子&amp;lt;/li&amp;gt;&#13;
                        &amp;lt;/ul&amp;gt;&#13;
                        &amp;lt;img src="{{ url_for('static', filename='img/index.jpg') }}" class="rounded img-fluid"&amp;gt;&#13;
                    &amp;lt;/div&amp;gt;&#13;
                    &amp;lt;div class="col-md-4"&amp;gt;&#13;
                        &amp;lt;div class="card mb-3 w-100 bg-light"&amp;gt;&#13;
                            &amp;lt;div class="card-header"&amp;gt;&amp;lt;h4 class="text-muted"&amp;gt;&amp;lt;strong&amp;gt;用户注册&amp;lt;/strong&amp;gt;&amp;lt;/h4&amp;gt;&amp;lt;/div&amp;gt;&#13;
                            &amp;lt;div class="card-body"&amp;gt;&#13;
                                {% include "_flash.html" %}&#13;
                                &amp;lt;form class="bs-component" action="/auth/register/" method="post"&amp;gt;&#13;
                                    {{ form.csrf_token }}&#13;
                                    {{ wtf.form_field(form.user_name) }}&#13;
                                    {{ wtf.form_field(form.nickname) }}&#13;
                                    {{ wtf.form_field(form.user_email) }}&#13;
                                    {{ wtf.form_field(form.password) }}&#13;
                                    {{ wtf.form_field(form.confirm_pwd) }}&#13;
                                    {{ wtf.form_field(form.colleges) }}&#13;
                                    &amp;lt;label for="captcha"&amp;gt;验证码&amp;lt;/label&amp;gt;&#13;
                                    &amp;lt;div class="input-group"&amp;gt;&#13;
                                        &amp;lt;input type="text" class="form-control" name="captcha" id="captcha" placeholder="请输入验证码" aria-required="true" aria-describedby="captcha" required&amp;gt;&#13;
                                        &amp;lt;div class="input-group-append"&amp;gt;&#13;
                                            &amp;lt;button class="btn btn-success" onclick="sendCapt()" id="sendCaptcha"&amp;gt;发送&amp;lt;/button&amp;gt;&#13;
                                        &amp;lt;/div&amp;gt;&#13;
                                    &amp;lt;/div&amp;gt;&#13;
                                    &amp;lt;p class="p-hint"&amp;gt;验证码发送成功,10分钟内有效!&amp;lt;/p&amp;gt;&#13;
                                    {{ form.submit }}&#13;
                                    &amp;lt;hr&amp;gt;&#13;
                                    &amp;lt;small&amp;gt;已有账号? &amp;lt;a style="text-decoration: none;" href="{{ url_for('.login') }}"&amp;gt;登录.&amp;lt;/a&amp;gt;&#13;
                                    &amp;lt;/small&amp;gt;&#13;
                                &amp;lt;/form&amp;gt;&#13;
                            &amp;lt;/div&amp;gt;&#13;
                        &amp;lt;/div&amp;gt;&#13;
                    &amp;lt;/div&amp;gt;&#13;
                &amp;lt;/div&amp;gt;&#13;
            &amp;lt;/div&amp;gt;&#13;
        &amp;lt;/div&amp;gt;&#13;
    &amp;lt;/main&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;然后打开&lt;strong&gt;&lt;code&gt;bbs/blueprint/frontend/auth.py&lt;/code&gt;&lt;/strong&gt;文件,接着在上一节下面嵌入如下代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@auth_bp.route('/register/', methods=['GET', 'POST'])&#13;
def register():&#13;
    colleges = College.query.all()&#13;
    form = RegisterForm()&#13;
    return render_template('frontend/register.html', colleges=colleges, form=form)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在后端代码中我们通过&lt;code&gt;render_template&lt;/code&gt;函数返回了前端注册页面,并且携带注册表单的实例参数。在前端html文件中,我们可以通过form.参数名的方式来进行表单渲染。同时还在前端文件中导入了&lt;code&gt;bootstrap/wtf.html&lt;/code&gt;,这样就可以将表单的样式渲染成&lt;code&gt;bootstrap&lt;/code&gt;的样式,当然也可以不是bootstrap/wtf.html来渲染,在后端表单类中可以通过&lt;code&gt;render_kw&lt;/code&gt;参数来指定我们表单的一些特定参数。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在前端页面中我们还手动加入了一行验证码输入框,点击发送按钮就可以将验证码发送到用户填写的邮箱当中去了。为什么不将此输入框写到后端表单中去?因为那样不好处理前端样式了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;访问http://127.0.0.1/auth/register/ 将会看到如下页面:&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://pic4.zhimg.com/80/v2-9dcef0c7f76a95b78c06063259642503_720w.jpg" style="margin:0px auto; width:1902px" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在注册页面中是需要用户填写邮箱收到的验证码,因此我们需要在后端代码中实现发送邮件的功能。发送邮件的功能是通过flask-email来实现的,打开bbs/extensions.py文件,加入下面的代码,然后在&lt;code&gt;&lt;strong&gt;__init__.py&lt;/strong&gt;&lt;/code&gt;文件中进行注册。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask_mail import Mail&#13;
mail = Mail()&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在使用发送邮件功能之前,首先我们需要到qq邮箱或者网易邮箱或者其他可以使用的邮箱申请SMTP服务,具体流程可以某度某歌搜索一下,这里就不再累述。将申请到的私密信填入到&lt;code&gt;.env&lt;/code&gt;文件中&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-ini"&gt;MAIL_SERVER='smtp.qq.com'&#13;
MAIL_USERNAME='你的qq邮箱名'&#13;
MAIL_PASSWORD='qq邮箱秘钥不是登录密码是申请SMTP那串无规则秘钥'&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;然后在&lt;code&gt;&lt;strong&gt;bbs/setting.py&lt;/strong&gt;&lt;/code&gt;文件中加入下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class BaseConfig(object):    &#13;
    # 省略之前代码&#13;
    BBS_MAIL_SUBJECT_PRE = '[狗子学院]'&#13;
    MAIL_SERVER = os.getenv('MAIL_SERVER')&#13;
    MAIL_PORT = 465&#13;
    MAIL_USE_SSL = True&#13;
    MAIL_USERNAME = os.getenv('MAIL_USERNAME')&#13;
    MAIL_PASSWORD = os.getenv('MAIL_PASSWORD')&#13;
    MAIL_DEFAULT_SENDER = ('BBS Admin', MAIL_USERNAME)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;新建&lt;code&gt;&lt;strong&gt;bbs/email.py&lt;/strong&gt;&lt;/code&gt;文件,并将下面代码写入其中。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from threading import Thread&#13;
&#13;
from bbs.extensions import mail&#13;
from flask_mail import Message&#13;
from flask import current_app, render_template&#13;
&#13;
&#13;
def async_send_mail(app, msg):&#13;
    with app.app_context():&#13;
        mail.send(msg)&#13;
&#13;
&#13;
def send_email(to_mail, subject, template, **kwargs):&#13;
    message = Message(current_app.config['BBS_MAIL_SUBJECT_PRE'] + subject,&#13;
                      recipients=[to_mail],&#13;
                      sender=current_app.config['MAIL_USERNAME'])&#13;
    message.body = render_template(template + '.txt', **kwargs)&#13;
    message.html = render_template(template + '.html', **kwargs)&#13;
    th = Thread(target=async_send_mail, args=(current_app._get_current_object(), message))&#13;
    th.start()&#13;
    return th&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在send_email函数中,使用了render_template()来渲染了邮件消息body以及html参数,因此需要先将这两个模板准备好。新建&lt;code&gt;&lt;strong&gt;bbs/templates/email/verifyCode.html&lt;/strong&gt;&lt;/code&gt;&amp;nbsp;与&lt;code&gt;bbs/templates/email/verifyCode.txt&lt;/code&gt;&amp;nbsp;文件,将下面的代码写到文件中去&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;verifyCode.html&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;&amp;lt;h3 style="font-weight: bold;font-size: 18px"&amp;gt;Hello {{ username }},&amp;lt;/h3&amp;gt;&#13;
&amp;lt;p&amp;gt;Welcome to join the &amp;lt;a href="http://bbs.2dogz.cn"&amp;gt;狗子学院&amp;lt;/a&amp;gt;!This is your register captcha below here.&amp;lt;/p&amp;gt;&#13;
&amp;lt;h1&amp;gt;&amp;lt;strong&amp;gt;{{ ver_code }}&amp;lt;/strong&amp;gt;&amp;lt;/h1&amp;gt;&#13;
&amp;lt;h5&amp;gt;&amp;lt;b&amp;gt;&amp;lt;i&amp;gt;The captcha will expire after 10 minutes.&amp;lt;/i&amp;gt;&amp;lt;/b&amp;gt;&amp;lt;/h5&amp;gt;&#13;
&amp;lt;p style="color: red; font-style: italic"&amp;gt; If this operate is not by yourself, please change your password right now!Maybe your account was cracked.&amp;lt;/p&amp;gt;&#13;
&amp;lt;small&amp;gt;(Please do not reply to this notification, this inbox is not monitored.)&amp;lt;/small&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;code&gt;&lt;strong&gt;verifyCode.txt&lt;/strong&gt;&lt;/code&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-todotxt"&gt;Hello {{ username }}&#13;
Welcome to Blogin!&#13;
Welcome to join the 狗子学院!This is your register captcha below here.&#13;
{{ ver_code }}&#13;
The captcha will expire after 10 minutes.&#13;
If this operate is not by yourself, please change your password right now!Maybe your account was cracked.&amp;lt;/p&amp;gt;&#13;
&#13;
(Please do not reply to this notification, this inbox is not monitored.)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;然后开始编写发送邮件的后端逻辑代码,新建&lt;code&gt;&lt;strong&gt;bbs/blueprint/frontend/normal.py&lt;/strong&gt;&lt;/code&gt;&amp;nbsp;文件,因为发送邮件属于通用行为,因此将其放入normal.py模块中,将以下代码嵌入其中&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask import Blueprint, send_from_directory, request, jsonify&#13;
from bbs.extensions import db&#13;
from bbs.email import send_email&#13;
from bbs.models import VerifyCode, Gender, Role, College&#13;
&#13;
@normal_bp.route('/send-email/', methods=['POST'])&#13;
def send():&#13;
    to_email = request.form.get('user_email')&#13;
    username = request.form.get('user_name')&#13;
    ver_code = generate_ver_code()&#13;
    send_email(to_mail=to_email, subject='Captcha', template='email/verifyCode', username=username,&#13;
               ver_code=ver_code)&#13;
&#13;
    # 判断是否已经存在一个最新的可用的验证码,以确保生效的验证码是用户收到最新邮件中的验证码&#13;
    exist_code = VerifyCode.query.filter(VerifyCode.who == to_email, VerifyCode.is_work == 1).order_by(&#13;
        VerifyCode.timestamps.desc()).first()&#13;
    if exist_code:&#13;
        exist_code.is_work = False&#13;
    nt = datetime.datetime.now()&#13;
    et = nt + datetime.timedelta(minutes=10)&#13;
    verify_code = VerifyCode(val=ver_code, who=to_email, expire_time=et)&#13;
    db.session.add(verify_code)&#13;
    db.session.commit()&#13;
    return jsonify({'tag': 1, 'info': '邮件发送成功!'})&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;我们根据请求中的keyword来获取前端发送过来的请求参数,然后调用send_email()函数进行发送邮件,同时将生成的随机验证码放入到邮件消息体中去。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;然后判断数据库中是否已经存在了属于该注册用户的验证码,如果有则将它设置为过期的,然后将新的验证码存入到数据库中,并设置过期时间为10分钟。这里是通过MySQL来保存的验证码信息,也有其他方法来保存验证码信息,比如使用redis来保存,redis可以设置字段过期时间,如果达到了这个时间,再去取这个字段的值就会为None。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;接着我们来处理前端发送邮件的代码,打开bbs/templates/frontend/register.html文件,加入下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;&amp;lt;main&amp;gt;&#13;
...&#13;
&amp;lt;/main&amp;gt;&#13;
&amp;lt;script&amp;gt;&#13;
        let time = 60;&#13;
        let reg = /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/;&#13;
&#13;
        function sendCapt(){&#13;
            let $sendBtn = $("#sendCaptcha");&#13;
            let $email = $("#user_email");&#13;
            let $username = $("#user_name");&#13;
            if ($username.val() === '' || $email.val() === '' || !reg.test($email.val())){&#13;
                return false;&#13;
            }&#13;
            $sendBtn.attr('disabled', true);&#13;
            getRandomCode($sendBtn);&#13;
            $.ajax({&#13;
                url: '/normal/send-email/',&#13;
                type: 'post',&#13;
                data: {'user_name': $username.val(), 'user_email': $email.val()},&#13;
                success: function (res){&#13;
                    if (res.tag){&#13;
                        $(".p-hint").slideDown(500).delay(3000).hide(500);&#13;
                    }&#13;
                }&#13;
            })&#13;
        }&#13;
&#13;
        //倒计时&#13;
        function getRandomCode(obj) {&#13;
            if (time === 0) {&#13;
                time = 60;&#13;
                obj.text('发送');&#13;
                obj.attr('disabled', false);&#13;
                return;&#13;
            } else {&#13;
                time--;&#13;
                obj.text(time+'(秒)');&#13;
            }&#13;
            setTimeout(function() {&#13;
                getRandomCode(obj);&#13;
            },1000);&#13;
        }&#13;
    &amp;lt;/script&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;通过ajax向后端&lt;code&gt;&lt;strong&gt;/normal/send-email/&lt;/strong&gt;&lt;/code&gt;发送请求,同时将&lt;code&gt;email&lt;/code&gt;与&lt;code&gt;username&lt;/code&gt;传递到后端,同时将发送验证码按钮置为不可点击状态,间隔60s才能发送一次,并在前端页面给用户一个提示信息。这样发送验证码邮件的整个流程就完成了,接下里要处理用户点击注册按钮之后的逻辑。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;我们使用的是wtfforms来渲染的后端表单,并且在某些表单字段中加入了一些限制信息。在后端代码中,我们可以通过wtfforms的示例来验证我们的表单,打开&lt;strong&gt;&lt;code&gt;bbs/blueprint/frontend/auth.py&lt;/code&gt;&lt;/strong&gt;&amp;nbsp;加入下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@auth_bp.route('/register/', methods=['GET', 'POST'])&#13;
def register():    &#13;
    ...&#13;
    if form.validate_on_submit():&#13;
        username = form.user_name.data&#13;
        nickname = form.nickname.data&#13;
        password = form.confirm_pwd.data&#13;
        email = form.user_email.data&#13;
        college = form.colleges.data&#13;
        captcha = request.form.get('captcha')&#13;
        code = VerifyCode.query.filter(VerifyCode.who == email, VerifyCode.is_work == 1).order_by(&#13;
            VerifyCode.timestamps.desc()).first()&#13;
        if code:&#13;
            if code.val != int(captcha):&#13;
                flash('验证码错误!', 'danger')&#13;
                return redirect(request.referrer)&#13;
            elif code.expire_time &amp;lt; datetime.datetime.now():&#13;
                flash('验证码已过期!', 'danger')&#13;
                return redirect(request.referrer)&#13;
        else:&#13;
            flash('请先发送验证码到邮箱!', 'info')&#13;
            return redirect(request.referrer)&#13;
&#13;
        user = User(username=username,&#13;
                    college_id=college,&#13;
                    nickname=nickname,&#13;
                    email=email,&#13;
                    password=password,&#13;
                    status_id=1)&#13;
        user.generate_avatar()&#13;
        user.set_password(password)&#13;
        code.is_work = False&#13;
        db.session.add(user)&#13;
        db.session.commit()&#13;
        flash('注册成功,欢迎加入二狗学院!', 'success')&#13;
        return redirect(url_for('.login'))&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;通过&lt;code&gt;form.validate_on_submit()&lt;/code&gt;来判断提交的表单是否通过了验证,通过验证之后通过&lt;code&gt;form.字段名.data&lt;/code&gt;来获取对应表单字段的值,然后根据邮箱来查找上一步数据库保存的验证码,如果不存在验证码,则提示用户发送验证码到邮箱,因为存在着一种可能用户乱填一个验证码,而不发送验证码到邮箱。然后判断验证码是否正确或者过期,如果未通过,则发送对应的提示消息提示用户,如果通过,则将用户信息保存到数据库中,然后重定向到登录页面。至此,用户注册的功能就已经完成了。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;用户登录&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;相比注册功能,用户登录就比较简单了。新建&lt;code&gt;&lt;strong&gt;bbs/templates/frontend/login.html&lt;/strong&gt;&lt;/code&gt;文件,该文件为用户登录的前端页面模板文件,同样的我们使用wtfforms来渲染表单,在文件中嵌入下面的代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;{% extends "frontend/base.html" %}&#13;
{% import 'bootstrap/wtf.html' as wtf %}&#13;
{% block title %}&#13;
    用户登录&#13;
{% endblock %}&#13;
{% block content %}&#13;
    &amp;lt;body&amp;gt;&#13;
    &amp;lt;main&amp;gt;&#13;
        &amp;lt;div class="container"&amp;gt;&#13;
            {% include "_flash.html" %}&#13;
            &amp;lt;div class="jumbotron pt-5 pb-3 mt-5"&amp;gt;&#13;
                &amp;lt;div class="row"&amp;gt;&#13;
                    &amp;lt;div class="col-md-8"&amp;gt;&#13;
                        &amp;lt;img src="{{ url_for('static', filename='img/index.jpg') }}" class="rounded img-fluid"&amp;gt;&#13;
                    &amp;lt;/div&amp;gt;&#13;
                    &amp;lt;div class="col-md-4"&amp;gt;&#13;
                        &amp;lt;div class="card mb-3 w-100 bg-light align-self-center"&amp;gt;&#13;
                            &amp;lt;div class="card-header"&amp;gt;&amp;lt;h4 class="text-muted"&amp;gt;&amp;lt;strong&amp;gt;用户登录&amp;lt;/strong&amp;gt;&amp;lt;/h4&amp;gt;&amp;lt;/div&amp;gt;&#13;
                            &amp;lt;div class="card-body "&amp;gt;&#13;
                                &amp;lt;form class="bs-component" action="/auth/login/" method="post"&amp;gt;&#13;
                                    {{ form.csrf_token }}&#13;
                                    {{ wtf.form_field(form.usr_email) }}&#13;
                                    {{ wtf.form_field(form.password) }}&#13;
                                    {{ wtf.form_field(form.remember_me) }}&#13;
                                    {{ form.submit }}&#13;
                                    &amp;lt;hr&amp;gt;&#13;
                                    &amp;lt;small&amp;gt;没有账号? &amp;lt;a style="text-decoration: none;" href="{{ url_for('.register') }}"&amp;gt;注册.&amp;lt;/a&amp;gt;&#13;
                                    &amp;lt;/small&amp;gt;&#13;
                                &amp;lt;/form&amp;gt;&#13;
                            &amp;lt;/div&amp;gt;&#13;
                        &amp;lt;/div&amp;gt;&#13;
                    &amp;lt;/div&amp;gt;&#13;
                &amp;lt;/div&amp;gt;&#13;
            &amp;lt;/div&amp;gt;&#13;
        &amp;lt;/div&amp;gt;&#13;
    &amp;lt;/main&amp;gt;&#13;
    &amp;lt;/body&amp;gt;&#13;
{% endblock %}&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;代码中的&lt;code&gt;form.csrf_token&lt;/code&gt;&amp;nbsp;是一种防止csrf攻击的手段,关于csrf攻击具体可以百度,之后的代码就跟注册模板一样,通过wtfforms进行表单渲染,因此我们需要新建一个渲染登录表单的类,打开bbs/forms.py模块,加入新建登录表单的代码,如下所示&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class LoginForm(FlaskForm):&#13;
    usr_email = StringField(u'邮箱/用户名', validators=[DataRequired(message='用户名或邮箱不能为空')],&#13;
                            render_kw={'placeholder': '请输入邮箱或用户名'})&#13;
    password = StringField(u'登录密码',&#13;
                           validators=[DataRequired(message='登录密码不能为空'),&#13;
                                       Length(min=8, max=40, message='登录密码必须在8-40位之间')],&#13;
                           render_kw={'type': 'password', 'placeholder': '请输入用户密码'})&#13;
    remember_me = BooleanField(u'记住我')&#13;
    submit = SubmitField(u'登录', render_kw={'class': 'source-button btn btn-primary btn-xs'})&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;接着需要处理后端的登录视图函数,打开&lt;code&gt;bbs/blueprint/frontend/auth.py&lt;/code&gt;模块,新建一个视图函数,代码如下所示&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask_login import current_user, login_user, logout_user&#13;
@auth_bp.route('/login/', methods=['GET', 'POST'])&#13;
def login():&#13;
    if current_user.is_authenticated:&#13;
        return redirect(url_for('index_bp.index'))&#13;
    form = LoginForm()&#13;
    if form.validate_on_submit():&#13;
        usr = form.usr_email.data&#13;
        pwd = form.password.data&#13;
        user = User.query.filter(or_(User.username == usr, User.email == usr.lower())).first()&#13;
        if user is not None and user.status.name == '禁用':&#13;
            flash('您的账号处于封禁状态,禁止登陆!联系管理员解除封禁!', 'danger')&#13;
            return redirect(url_for('.login'))&#13;
&#13;
        if user is not None and user.check_password(pwd):&#13;
            if login_user(user, form.remember_me.data):&#13;
                flash('登录成功!', 'success')&#13;
                return redirect(url_for('index_bp.index'))&#13;
        elif user is None:&#13;
            flash('无效的邮箱或用户名.', 'danger')&#13;
        else:&#13;
            flash('无效的密码', 'danger')&#13;
    return render_template('frontend/login.html', form=form)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;关于登录逻辑处理,flask有一个十分流行好用的第三方库flask-login,使用该第三库我们可以很方便的处理登录、退出权限控制等操作。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;首先通过current_user来判断用户是否已经登录了,如果登录则返回主页面。接着通过LoginForm示例来获取前端登录页面传递过来的值,从数据库获取用户的相关信息,首先判断账号是否被禁用了,如果被禁用则弹出提示信息,并返回给前端页面。接着判断用户密码是否匹配,如果不匹配则返回登录页面,并提示用户密码不匹配,反之则重定向到主页。&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;这里的登录成功重定向其实可以做的更加人性化,当用户进入到需要登录才能操作的页面时候,这时候会自动跳转到登录页面。如果用户登录成功,应该是返回前一个页面而不是固定返回主页。flask_login的login_required装饰器重定向到登录页面的时候会带一个next参数,因此我们可以通过此参数来让用户登录成功之后重定向到上一页,具体实现很简单,就请读者自主开发吧!&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;这时候我们打开登录页面,可以看到如下页面&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://pic3.zhimg.com/80/v2-1c206b89e8d4fbf7b2abebea969f320a_720w.jpg" style="margin:0px auto; width:720px" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;到此,用户登录注册功能就已经全部实现了,下一节将开始讲述论坛主页的实现。&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/23/" rel="alternate"/>
  </entry>
  <entry>
    <id>22</id>
    <title>[系列教程]使用Flask搭建一个校园论坛3-登录注册</title>
    <updated>2023-09-27T02:08:19.928953+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h2&gt;1.知识预览&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;在本章节中,将学习到以下内容:&lt;/p&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;flask-sqlachemy 数据库orm的使用&lt;/li&gt;&#13;
	&lt;li&gt;click 注册命令,初始化项目基础数据&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&#13;
&lt;h2&gt;2.轮子&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;Flask本身的定义是一个微框架,何为其义呢?&amp;ldquo;微&amp;rdquo;并不代表整个应用只能塞在一个 Python 文件内, 当然塞在单一文件内也没有问题。 &amp;ldquo;微&amp;rdquo;也不代表 Flask 功能不强。 微框架中的&amp;ldquo;微&amp;rdquo;字表示 Flask 的目标是保持核心简单而又可扩展。 Flask 不会替你做出许多决定,比如选用何种数据库。 类似的决定,如使用何种模板引擎,是非常容易改变的。 Flask 可以变成你任何想要的东西,一切恰到好处,由你做主。因此,可以在开源的世界中找到非常多的flask扩展,本项目中使用的orm框架是&lt;code&gt;flask-sqlachemy&lt;/code&gt;,基于&lt;code&gt;sqlachemy&lt;/code&gt;开发的flask扩展。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在bbs目录下新建&lt;code&gt;extensions.py&lt;/code&gt;模块,这个模块主要是用来存放我们第三方拓展使用。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;bbs/extensions.py&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask_sqlalchemy import SQLAlchemy&#13;
db = SQLAlchemy()&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h2&gt;3.项目配置&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;项目可以有开发环境、测试环境、生产环境等,不同环境所使用的配置肯定也是不同的,同时项目也有一些配置是通用的。在bbs目录下面新建setting.py模块,该模块用来存储项目所有配置参数。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在第三小节中,我们使用了flask-salachemy这个框架,使用这个框架之前,有一些默认参数需要我们进行配置,我们将下面的代码写入到&lt;code&gt;setting.py&lt;/code&gt;文件中去。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;bbs/setting.py&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;import os&#13;
&#13;
basedir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))&#13;
&#13;
&#13;
class BaseConfig(object):&#13;
    SQLALCHEMY_TRACK_MODIFICATIONS = False&#13;
&#13;
    DATABASE_USER = os.getenv('DATABASE_USER')&#13;
    DATABASE_PWD = os.getenv('DATABASE_PWD')&#13;
    DATABASE_HOST = os.getenv('DATABASE_HOST')&#13;
    DATABASE_PORT = os.getenv('DATABASE_PORT')&#13;
&#13;
&#13;
class DevelopmentConfig(BaseConfig):&#13;
    SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{}:{}@{}/bbs?charset=utf8mb4'.format(BaseConfig.DATABASE_USER,&#13;
                                                                                    BaseConfig.DATABASE_PWD,&#13;
                                                                                    BaseConfig.DATABASE_HOST)&#13;
    # REDIS_URL = "redis://localhost"&#13;
    REDIS_URL = "redis://localhost:6379"&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;首先通过内置的os模块获取了项目的根目录,同时新建了一个BaseConfig类,在该类中定义了一些参数(配置参数名称一般使用大写命名),由于数据库连接的参数都属于是比较高危的数据,因此将其保存在环境变量中,通过&lt;code&gt;os.getenv()&lt;/code&gt;去获取这些参数的具体数值。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;code&gt;DevelopmentConfig&lt;/code&gt;类继承于&lt;code&gt;BaseConfig&lt;/code&gt;类,该类是在开发环境中使用的。在该类中定义了&lt;code&gt;SQLALCHEMY_DATABASE_URI&lt;/code&gt;参数,flask-sqlachemy在初始化时,会自动根据该参数去连接数据库。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;数据库连接参数都保存在环境变量中,有以下两种方式将其保存至环境变量:&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;使用export命令(Linux)&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;export DATABASE_USER=root&#13;
export DATABASE_PWD=123456&#13;
export DATABASE_HOST=127.0.0.1&#13;
export DATABASE_PORT=3306&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;使用该方式的弊端就是我们每次都需要手动去执行这些命令。&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;.env文件&lt;br /&gt;&#13;
	可以将这些参数保存到.env文件中,然后通过&lt;code&gt;python-dotenv&lt;/code&gt;库自动加载.env文件中的内容到环境变量。在&lt;code&gt;university-bbs&lt;/code&gt;目录下新建&lt;code&gt;.env&lt;/code&gt;文件,并嵌入下面的代码。&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-ini"&gt;DATABASE_USER=weijiang&#13;
DATABASE_PWD=1994124&#13;
DATABASE_HOST=127.0.0.1&#13;
DATABASE_PORT=3306&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;如果没有自动加载我们可以在&lt;code&gt;setting.py&lt;/code&gt;模块顶部中加入如下代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from dotenv import load_dotenv&#13;
load_dotenv('.env')&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h2&gt;4.表的创建&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;在完成了上面的准备工作之后就可以开始创建数据库表了。作为一个&lt;code&gt;CMS系统&lt;/code&gt;,数据库的使用是必不可少的,在这里采用的&lt;code&gt;MySQL&lt;/code&gt;数据库,当然你也可以使用其他的关心数据库,如&lt;code&gt;MariaDB&lt;/code&gt;等。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在&lt;code&gt;bbs&lt;/code&gt;目录下新建一个&lt;code&gt;models.py&lt;/code&gt;模块,该模块主要是用来定义我们的数据库表模型用的,嵌入以下代码&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;bbs/models.py&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class User(db.Model, UserMixin):&#13;
    __tablename__ = 't_user'&#13;
&#13;
    id = db.Column(db.INTEGER, primary_key=True, nullable=False, index=True, autoincrement=True)&#13;
    username = db.Column(db.String(40), nullable=False, index=True, unique=True, comment='user name')&#13;
    nickname = db.Column(db.String(40), nullable=False, unique=True, comment='user nick name')&#13;
    password = db.Column(db.String(256), comment='user password')&#13;
    email = db.Column(db.String(128), unique=True, nullable=False, comment='user register email')&#13;
    slogan = db.Column(db.String(40), default='')&#13;
    website = db.Column(db.String(128), default='', comment="user's website")&#13;
    location = db.Column(db.String(128), default='', comment='user location')&#13;
    avatar = db.Column(db.String(100), nullable=False, comment='user avatar')&#13;
    avatar_raw = db.Column(db.String(100), comment='use avatar raw file')&#13;
    create_time = db.Column(db.DATETIME, default=datetime.datetime.now)&#13;
&#13;
    status_id = db.Column(db.INTEGER, db.ForeignKey('t_status.id'))&#13;
    college_id = db.Column(db.INTEGER, db.ForeignKey('t_college.id'))&#13;
    role_id = db.Column(db.INTEGER, db.ForeignKey('t_role.id'), default=3, comment='user role id default is 3 '&#13;
    &#13;
    college = db.relationship('College', back_populates='user')&#13;
    role = db.relationship('Role', back_populates='user')&#13;
    status = db.relationship('Status', back_populates='user')                                                                                      'that is student role')&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在上面代码中定义了一个User类,并且让它继承自&lt;code&gt;Model&lt;/code&gt;、&lt;code&gt;UserMiXin&lt;/code&gt;两个基类。之后在类中定义了一些参数,其中使用&lt;code&gt;db.Column&lt;/code&gt;定义就是表的字段,使用&lt;code&gt;db.relationship&lt;/code&gt;定义是表与表之间的关系。在有些字段中我们使用了db.Foreignkey,这表示该字段是一个外键。通过外键关系,可以保证数据唯一性与完整性。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在这种小项目中使用外键可以提高开发效率,但如果项目量级大,并且业务并发量大,就不要去使用外键这种数据库层面的逻辑维护,可以将数据完整性维护添加到业务代码中去。&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;外键的字段命名没有明确的要求,因为使用的其他表的&lt;code&gt;主键id&lt;/code&gt;作为外键进行连接,因此为了方便阅读,使用&lt;code&gt;表名_id&lt;/code&gt;的形式进行命名,同时ForeignKey类传入参数为&lt;code&gt;表名.字段名&lt;/code&gt;。模型类对应的表名由flask-sqlachemy自动生成,如果你在模型类中定义了__&lt;em&gt;tablename&lt;/em&gt;__ 参数,则会使用该参数的值作为表名。&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;在User表中,我们使用了三个外键,分别是status_id、college_id、role_id,因此需要新建Status、College、Role三个表模型,同时role跟permission又是外键关系,因此还需要创建permission模型,在models.py模块中嵌入如下代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class College(db.Model):&#13;
    __tablename__ = 't_college'&#13;
&#13;
    id = db.Column(db.INTEGER, primary_key=True, nullable=False, autoincrement=True, index=True)&#13;
    name = db.Column(db.String(100), nullable=False)&#13;
    create_time = db.Column(db.DATETIME, default=datetime.datetime.now)&#13;
&#13;
    user = db.relationship('User', back_populates='college', cascade='all')&#13;
&#13;
&#13;
class Role(db.Model):&#13;
    __tablename__ = 't_role'&#13;
&#13;
    id = db.Column(db.INTEGER, primary_key=True, autoincrement=True, index=True)&#13;
    name = db.Column(db.String(40), nullable=False)&#13;
    permission_id = db.Column(db.INTEGER, db.ForeignKey('t_permission.id'), nullable=False)&#13;
&#13;
    user = db.relationship('User', back_populates='role', cascade='all')&#13;
    permission = db.relationship('Permission', back_populates='role')&#13;
&#13;
&#13;
class Status(db.Model):&#13;
    __tablename__ = 't_status'&#13;
&#13;
    id = db.Column(db.INTEGER, primary_key=True, autoincrement=True, index=True)&#13;
    name = db.Column(db.String(40), nullable=False)&#13;
&#13;
    user = db.relationship('User', back_populates='status', cascade='all')&#13;
&#13;
&#13;
class Permission(db.Model):&#13;
    __tablename__ = 't_permission'&#13;
&#13;
    id = db.Column(db.INTEGER, primary_key=True, autoincrement=True, index=True)&#13;
    name = db.Column(db.String(40), nullable=False)&#13;
&#13;
    role = db.relationship('Role', back_populates='permission', cascade='all')&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;定义db.relationship()关系属性有什么作用呢?通过下图来进行解释。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://pic1.zhimg.com/80/v2-36a4d6f213b80651ba720551c4976d20_720w.jpg" style="width:375px" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;一个角色可以对应多个用户,一个用户只能对应一个角色,我们在Role模型类中定义了user关系变量,在User模型类中定义了role关系变量。&lt;/blockquote&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;user = User.query.filter_by(id=1).first()&#13;
print(user.role.name)&#13;
role = Role.query.filter_by(id=1).first()&#13;
print(role.user)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上面代码就解释了定义关系变量的作用,在一对多的关系中,在一端我们可以直接通过关系参数获取到对应多端的值,在多端关系参数返回是一个集合,在这个集合中我们可以获取到集合元素的所有值,大大的简化了我们工作。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;5.初始化基础数据&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;完成了上述模型类的定义后,我们需要在数据库中建立我们的表。在__&lt;em&gt;init&lt;/em&gt;__.py模块中加入下面的代码:&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from bbs.models import *&#13;
from bbs.extensions import db&#13;
def create_app(config_name=None):&#13;
    # 省略之前的代码&#13;
    app.config.from_object(DevelopmentConfig)&#13;
    register_extensions(app)&#13;
    return app&#13;
&#13;
  &#13;
def register_extensions(app: Flask):&#13;
    db.init_app(app)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在register_extensions()函数中初始化flask-sqlachemy,然后在工厂函数中进行注册调用。&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;有关Flask的第三方拓展一般都是使用的extension.init_app(app)的方式进行初始化注册!&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;接下来我们继续在__&lt;em&gt;init.py&lt;/em&gt;__模块中添加下面的代码,用作初始化项目的必须基础数据。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;def create_app(config_name=None):&#13;
    # 省略已有代码&#13;
    register_cmd(app)&#13;
    return app&#13;
&#13;
&#13;
def register_cmd(app: Flask):&#13;
    @app.cli.command()&#13;
    def init():&#13;
        click.confirm('这个操作会清空整个数据库,要继续吗?', abort=True)&#13;
        db.drop_all()&#13;
        click.echo('清空数据库完成!')&#13;
        db.create_all()&#13;
        init_status()&#13;
        click.echo('初始化状态表完成!')&#13;
        init_colleges()&#13;
        click.echo('初始化学院表完成!')&#13;
        init_permission()&#13;
        click.echo('初始化权限表完成!')&#13;
        init_role()&#13;
        click.echo('初始化角色表完成!')&#13;
        db.session.commit()&#13;
        click.echo('数据库初始化完成!')&#13;
&#13;
&#13;
def init_status():&#13;
    s1 = Status(name='正常')&#13;
    db.session.add(s1)&#13;
    s2 = Status(name='禁用')&#13;
    db.session.add(s2)&#13;
    db.session.commit()&#13;
&#13;
&#13;
def init_colleges():&#13;
    colleges = ['计算机科学与技术学院', '信息与通信工程学院', '法学院', '外国语学院', '体育学院', '生命科学学院', '文学院']&#13;
    for college in colleges:&#13;
        c = College(name=college)&#13;
        db.session.add(c)&#13;
    db.session.commit()&#13;
&#13;
&#13;
def init_permission():&#13;
    permissions = ['ALL', 'SOME', 'LITTLE']&#13;
    for per in permissions:&#13;
        p = Permission(name=per)&#13;
        db.session.add(p)&#13;
    db.session.commit()&#13;
&#13;
&#13;
def init_role():&#13;
    roles = ['超级管理员', '老师', '学生']&#13;
    r1 = Role(name=roles[0], permission_id=1)&#13;
    r2 = Role(name=roles[1], permission_id=2)&#13;
    r3 = Role(name=roles[2], permission_id=3)&#13;
    db.session.add(r1)&#13;
    db.session.add(r2)&#13;
    db.session.add(r3)&#13;
    db.session.commit()&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在上述代码中我们定义了一个&lt;code&gt;register_cmd(app)&lt;/code&gt;函数,同时在工厂函数中调用了它。在register_cmd函数中,&lt;strong&gt;通过app.cli.command()装饰器可以将函数admin()注册为一个flask命令&lt;/strong&gt;,在admin()函数中,做了数据库初始化的操作。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;通过使用前面定义的模型类,实例化对应的模型类对象,然后通过db.session.add(instance)添加到数据库,最后通过db.session.commit()函数将修改提交到数据库中。&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;在对数据库做了任何修改操作之后,我们都需要进行commit()操作!&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;确保激活了虚拟环境,在终端输入如下命令,就可以进行数据库初始化操作,通过终端输出的信息,可以看到数据库初始化的进度。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;flask init&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h2&gt;6. auth蓝图&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;完成上述工作之后就可以开始用户注册功能开发了。用户注册、登录等操作都属于验证操作,可以将其放入同一个功能蓝图中去,在&lt;code&gt;bbs/blueprint&lt;/code&gt;目录下新建&lt;code&gt;auth.py&lt;/code&gt;模块,嵌入如下代码。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask import Blueprint&#13;
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;别忘了在创建新的蓝图之后需要到&lt;strong&gt;create_app()&lt;/strong&gt;函数中去进行注册,否则路由会找不到对应的视图函数而抛出404错误!&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;注册、登录部分的功能我打算分为两个章节来写,如果一个章节过长,就会导致读者失去耐心,因此,本章节的内容就到这里了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;教程中的资源文件可以进入我的github仓库下载源代码使用&amp;nbsp;&lt;a href="https://link.zhihu.com/?target=https%3A//github.com/weijiang1994/university-bbs" target="_blank"&gt;仓库连接&lt;/a&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;下一节,我们将继续进行用户注册、登录功能的实现啦,尽请期待啦~~~&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/22/" rel="alternate"/>
  </entry>
  <entry>
    <id>21</id>
    <title>[系列教程]使用Flask搭建一个校园论坛2-基本框架</title>
    <updated>2023-09-27T02:08:19.928925+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;p&gt;在上一节中,我们介绍了整个项目的起因、功能设计等,这节开始,我们就是真正开始写(chao)代码了~~&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;1. 储备物资&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在阅读这个系列教程之前,我们需要在我们的脑海中储备以下知识:&lt;/p&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;Python编程语言的基本语法&lt;/li&gt;&#13;
	&lt;li&gt;Flask框架的基本用法&lt;/li&gt;&#13;
	&lt;li&gt;Jinja2模板引擎基本使用&lt;/li&gt;&#13;
	&lt;li&gt;Python ORM框架&lt;/li&gt;&#13;
	&lt;li&gt;HTML JS CSS 简单的了解&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&#13;
&lt;p&gt;有了以上的基础知识,我们就可以很顺利的阅读这个系列的教程啦~&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;2. 开始耕地&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;很多人都称我们程序员为码农码农,那我们就开始种地吧~&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;2.1 服务入口&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;相信阅读过flask文档的朋友都知道flask包含有以下两种启动方式&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;通过&lt;span class="marker"&gt;python脚本&lt;/span&gt;的方式运行&#13;
&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask import Flask&#13;
app = Flask(__name__)&#13;
&#13;
@app.route('/')&#13;
def index():&#13;
    return 'Hello, flask!'&#13;
&#13;
&#13;
if __name__ == '__main__':&#13;
    app.run()&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;通过&lt;span class="marker"&gt;命令行&lt;/span&gt;的方式&lt;br /&gt;&#13;
	使用该方式启动flask应用的时候,我们需要先在命令行窗口中输入如下命令:&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;export FLASK_APP=app&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
	&lt;blockquote class="m-blockquote"&gt;&#13;
	&lt;p&gt;然后通过&lt;span class="marker"&gt;flask run&lt;/span&gt; 命令来启动应用。&lt;br /&gt;&#13;
	&lt;br /&gt;&#13;
	注意:Windows 用户请将export替换成set&lt;/p&gt;&#13;
	&lt;/blockquote&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;p&gt;在此教程中,我们采用第二种方式来进行应用启动,因为我们项目内容相对比较多,&lt;span class="marker"&gt;都是CURD:)&lt;/span&gt;,应用的不同功能都分布在不同的模块中,使用flask的&lt;span class="marker"&gt;Blueprint&lt;/span&gt;来分割每个功能模块。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在&lt;span class="marker"&gt;bbs&lt;/span&gt;目录下新建&lt;span class="marker"&gt;__init__.py&lt;/span&gt;文件,该模块为我们的应用启动入口,在里面嵌入如下代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask import Flask&#13;
&#13;
def create_app():&#13;
    app = Flask('bbs')&#13;
&#13;
    @app.route('/')&#13;
    def index():&#13;
        return 'Hello, university bbs'&#13;
    return app&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;然后在控制台中输入2.1节的命令,访问 http://127.0.0.1:5000,我们就可以看到如下页面了,说明我们的flask应用已经成功启动了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="2-1" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/27722-1.png" style="height:108px; width:238px" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;上面的代码,我们创建了一个名为&lt;span class="marker"&gt;create_app &lt;/span&gt;的函数,在该函数中我们实例化了一个&lt;span class="marker"&gt;Flask对象&lt;/span&gt;,然后通过装饰器的方式注册了一个&lt;span class="marker"&gt;路由&amp;#39;/&amp;#39;&lt;/span&gt;,最后将这个Flask对象返回。当我们使用命令启动flask应用的时候,这个函数就是我们的入口函数,这种方式我们称作为工厂模式。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;2.2 开启debug&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;我们在开发过程中,肯定是修改代码之后就要立即调试,flask提供了debug模式,当我们开启debug的时候,我们修改完了代码,会auto reload 我们的应用,这样我们就不用每次修改了代码之后,手动去重新启动服务了,我们只需要在命令行中输入如下命令即可&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;export FLASK_ENV=development&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h2&gt;2.3 通用部分&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;我们的网站大概长成下面这个样子&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="页面布局" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/1267%E9%A1%B5%E9%9D%A2%E5%B8%83%E5%B1%80.png" style="height:514px; width:663px" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;其中,页眉和页脚在我们每个页面中都会出现,这样我们就可以将其抽取出来变为一个公共部分,又jinja2提供模板继承的功能,我们可以在其他页面中继承这些通用部分的内容。&lt;br /&gt;&#13;
在bbs/templates文件中我们新建一个&lt;span class="marker"&gt;frontend&lt;/span&gt;文件夹,然后新建一个名为&lt;span class="marker"&gt;base.html&lt;/span&gt;的文件,在该文件中嵌入以下内容:&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;bbs/templates/frontend/base.html&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;&amp;lt;!DOCTYPE html&amp;gt;&#13;
{% from "macro.html" import nav_item with context %}&#13;
&amp;lt;!--suppress ALL --&amp;gt;&#13;
&amp;lt;html lang="zh-hans"&amp;gt;&#13;
{% block head %}&#13;
    &amp;lt;head&amp;gt;&#13;
        &amp;lt;meta charset="UTF-8"&amp;gt;&#13;
        &amp;lt;title&amp;gt;{% block title %}{% endblock %}-二狗学院&amp;lt;/title&amp;gt;&#13;
        &amp;lt;meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"&amp;gt;&#13;
        &amp;lt;link rel="shortcut icon" href="{{url_for('static', filename='img/favorite.png')}}" type="image/x-icon"&amp;gt;&#13;
        &amp;lt;link rel="icon" href="{{ url_for('static', filename = 'img/favorite.png') }}" type="image/x-icon"&amp;gt;&#13;
        &amp;lt;script src="https://cdn.jsdelivr.net/npm/jquery@3.5.1/dist/jquery.min.js"&amp;gt;&amp;lt;/script&amp;gt;&#13;
        &amp;lt;script src="https://cdn.staticfile.org/popper.js/1.15.0/umd/popper.min.js"&amp;gt;&amp;lt;/script&amp;gt;&#13;
        &amp;lt;link href="https://cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"&amp;gt;&#13;
        &amp;lt;link rel="stylesheet" href="{{ url_for('static', filename='themes/darkly.bootstrap.min.css'}}"&amp;gt;&#13;
        &amp;lt;script src="{{ url_for('static', filename='validator/form-validation.js') }}"&amp;gt;&amp;lt;/script&amp;gt;&#13;
        &amp;lt;script src="https://cdn.staticfile.org/twitter-bootstrap/4.3.1/js/bootstrap.min.js"&amp;gt;&amp;lt;/script&amp;gt;&#13;
    &amp;lt;/head&amp;gt;&#13;
{% endblock %}&#13;
&#13;
{% block nav %}&#13;
    &amp;lt;nav class="navbar navbar-expand-lg navbar-dark bg-dark"&amp;gt;&#13;
        &amp;lt;div class="container align-self-end"&amp;gt;&#13;
            &amp;lt;a class="navbar-brand" href="/"&amp;gt;&amp;lt;i class="fa fa-bbs"&amp;gt;&amp;lt;/i&amp;gt;狗子学院&amp;lt;/a&amp;gt;&#13;
            &amp;lt;button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navHome" aria-controls="navHome" aria-expanded="false" aria-label="Toggle navigation"&amp;gt;&#13;
                &amp;lt;span class="navbar-toggler-icon"&amp;gt;&amp;lt;/span&amp;gt;&#13;
            &amp;lt;/button&amp;gt;&#13;
            &amp;lt;div class="collapse navbar-collapse" id="navHome"&amp;gt;&#13;
                &amp;lt;ul class="navbar-nav mr-auto"&amp;gt;&#13;
                    &amp;lt;li class="nav-item dropdown mr-5"&amp;gt;&#13;
                        &amp;lt;a class="nav-link dropdown-toggle" href="#" id="talk" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"&amp;gt;&amp;lt;i class="fa fa-fire mr-1"&amp;gt;&amp;lt;/i&amp;gt;大食堂&amp;lt;/a&amp;gt;&#13;
                        &amp;lt;div class="dropdown-menu" aria-labelledby="talk"&amp;gt;&#13;
                            &amp;lt;a class="dropdown-item" href="#"&amp;gt;杂谈&amp;lt;/a&amp;gt;&#13;
                            &amp;lt;a class="dropdown-item" href="#"&amp;gt;趣事&amp;lt;/a&amp;gt;&#13;
                            &amp;lt;a class="dropdown-item" href="#"&amp;gt;表白&amp;lt;/a&amp;gt;&#13;
                        &amp;lt;/div&amp;gt;&#13;
                    &amp;lt;/li&amp;gt;&#13;
                    &amp;lt;li class="nav-item dropdown mr-5"&amp;gt;&#13;
                        &amp;lt;a class="nav-link dropdown-toggle" href="#" id="talk" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"&amp;gt;&amp;lt;i class="fa fa-shopping-cart mr-1"&amp;gt;&amp;lt;/i&amp;gt;便利店&amp;lt;/a&amp;gt;&#13;
                        &amp;lt;div class="dropdown-menu" aria-labelledby="talk"&amp;gt;&#13;
                            &amp;lt;a class="dropdown-item" href="#"&amp;gt;寻物&amp;lt;/a&amp;gt;&#13;
                            &amp;lt;a class="dropdown-item" href="#"&amp;gt;咸鱼&amp;lt;/a&amp;gt;&#13;
                            &amp;lt;a class="dropdown-item" href="#"&amp;gt;活动&amp;lt;/a&amp;gt;&#13;
                        &amp;lt;/div&amp;gt;&#13;
                    &amp;lt;/li&amp;gt;&#13;
                    &amp;lt;li class="nav-item dropdown mr-5"&amp;gt;&#13;
                        &amp;lt;a class="nav-link dropdown-toggle" href="#" id="talk" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"&amp;gt;&amp;lt;i class="fa fa-delicious mr-1"&amp;gt;&amp;lt;/i&amp;gt;组织&amp;lt;/a&amp;gt;&#13;
                        &amp;lt;div class="dropdown-menu" aria-labelledby="talk"&amp;gt;&#13;
                            &amp;lt;a class="dropdown-item" href="#"&amp;gt;学院&amp;lt;/a&amp;gt;&#13;
                            &amp;lt;a class="dropdown-item" href="#"&amp;gt;社团&amp;lt;/a&amp;gt;&#13;
                            &amp;lt;a class="dropdown-item" href="#"&amp;gt;圈子&amp;lt;/a&amp;gt;&#13;
                        &amp;lt;/div&amp;gt;&#13;
                    &amp;lt;/li&amp;gt;&#13;
                &amp;lt;/ul&amp;gt;&#13;
                &amp;lt;form class="form-inline my-2 my-md-0"&amp;gt;&#13;
                    &amp;lt;input class="form-control" type="text" placeholder="请输入关键字" aria-label="Search" required&amp;gt;&#13;
                &amp;lt;/form&amp;gt;&#13;
            &amp;lt;/div&amp;gt;&#13;
        &amp;lt;/div&amp;gt;&#13;
    &amp;lt;/nav&amp;gt;&#13;
{% endblock %}&#13;
{% block content %}&#13;
{% endblock %}&#13;
{% block footer %}&#13;
    &amp;lt;footer class="container-fluid mt-4 py-0 bg-dark"&amp;gt;&#13;
        &amp;lt;div class="card-body text-center px-0 f-14"&amp;gt;&#13;
            &amp;lt;p class="card-text mb-1"&amp;gt;Copyright&amp;amp;nbsp;©&amp;amp;nbsp;&amp;lt;span&amp;gt;2020&amp;lt;/span&amp;gt;&#13;
                &amp;lt;a href="http://2dogz.cn/"  target="_blank" title="官网"&amp;gt;University BBS&amp;lt;/a&amp;gt;&amp;amp;nbsp;Design&amp;amp;nbsp;by&amp;amp;nbsp;Flask1.01.&#13;
            &amp;lt;/p&amp;gt;&#13;
        &amp;lt;/div&amp;gt;&#13;
    &amp;lt;/footer&amp;gt;&#13;
{% endblock %}&#13;
{% block script %}&#13;
    &amp;lt;script&amp;gt;&#13;
        $(function () {&#13;
            $('[data-toggle="tooltip"]').tooltip()&#13;
        })&#13;
    &amp;lt;/script&amp;gt;&#13;
{% endblock %}&#13;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;代码释义:&lt;/p&gt;&#13;
&#13;
&lt;p&gt;我们在base.html 文件中写了几个&lt;code&gt;&lt;span class="marker"&gt;block&lt;/span&gt;&lt;/code&gt;块定义,分别是&lt;span class="marker"&gt;head&lt;/span&gt;、&lt;span class="marker"&gt;nav&lt;/span&gt;、&lt;span class="marker"&gt;title&lt;/span&gt;、&lt;span class="marker"&gt;content&lt;/span&gt;、&lt;span class="marker"&gt;footer&lt;/span&gt;以及&lt;span class="marker"&gt;script&lt;/span&gt;。分别用来定义我们的html文件的&lt;span class="marker"&gt;引用&lt;/span&gt;、&lt;span class="marker"&gt;导航栏&lt;/span&gt;、&lt;span class="marker"&gt;标题&lt;/span&gt;、&lt;span class="marker"&gt;内容&lt;/span&gt;、&lt;span class="marker"&gt;页脚&lt;/span&gt;以及&lt;span class="marker"&gt;js代码块&lt;/span&gt;。通过&lt;span class="marker"&gt;{% block name&amp;nbsp;%}&lt;/span&gt;,当我们的子模板继承自&lt;span class="marker"&gt;base.html&lt;/span&gt;模板的时候,我们子模板可以重写这个&lt;span class="marker"&gt;block&lt;/span&gt;的定义,当然也可以使用&lt;span class="marker"&gt;{{ super()&amp;nbsp;}}&lt;/span&gt;函数来继承父模板的定义,然后自己定义新的内容。&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;在head中引用了项目需要引入的框架,比如jQuery、bootstrap4等;&lt;/li&gt;&#13;
	&lt;li&gt;在nav中我们使用bootstrap的导航栏组件轻松完成了顶部导航栏的构建&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;h2&gt;2.4 主页&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;我们写完了页面的通用部分之后,就可以在主页中继承该模板了。在&lt;span class="marker"&gt;bbs/templates/frontend/&lt;/span&gt;文件夹中新建&lt;span class="marker"&gt;index.html&lt;/span&gt;文件,并嵌入下面的代码&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;bbs/templates/frontend/index.html&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;{% extends "frontend/base.html" %}&#13;
{% from "macro.html" import post_item, render_pagination with context%}&#13;
{% block title %}&#13;
    主页&#13;
{% endblock %}&#13;
{% block content %}&#13;
    &amp;lt;main&amp;gt;&#13;
        &amp;lt;div class="container mt-2"&amp;gt;&#13;
            &#13;
        &amp;lt;/div&amp;gt;&#13;
    &amp;lt;/main&amp;gt;&#13;
{% endblock %}&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;我使用extends关键字继承了基模板base.html,然后在content块中写了我们自己需要定义的内容。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在完成了我们主页模板代码之后,我们就需要通过路由将它渲染出来,并显示在用户的网页中。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在bbs目录下新建一个&lt;span class="marker"&gt;blueprint&lt;/span&gt;包,并新增模块&lt;span class="marker"&gt;index.py&lt;/span&gt;, 在其中嵌入如下代码&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;bbs/blueprint/index.py&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask import Blueprint, render_template&#13;
&#13;
index_bp = Blueprint('index_bp', __name__)&#13;
&#13;
&#13;
@index_bp.route('/')&#13;
@index_bp.route('/index/')&#13;
def index():&#13;
    return render_template('frontend/index.html')&#13;
&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;首先我们实例化了一个Blueprint对象,然后通过该对象注册了两个路由&amp;#39;/&amp;#39; 、&amp;#39;/index/&amp;#39;,并将这两个路由指向视图函数index,在index函数中,我们将我们之前创建的主页文件渲染,然后返回给客户端。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;我们在这里注册一个同样的路由,因此我们需要删除__init__.py模块中的index视图函数,同时我们在新建blueprint之后,需要将该蓝图进行注册,__init__.py最新代码如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask import Flask&#13;
from bbs.blueprint.index import index_bp&#13;
&#13;
&#13;
def create_app():&#13;
    app = Flask('bbs')&#13;
&#13;
    register_bp(app)&#13;
    return app&#13;
&#13;
&#13;
def register_bp(app: Flask):&#13;
    app.register_blueprint(index_bp)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;此时,我们访问http://127.0.0.1:5000看到的将是如下页面&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="主页" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/4527%E4%B8%BB%E9%A1%B5.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;2.5 错误处理&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;我们在访问网页的时候,经常会出现404,、500这种错误代码。如果我们使用flask提供的默认错误页面,是下面这种样子,如果用户不小心访问到了,将会一头雾水,不知所措,因此我们需要将特定错误页面进行处理,在用户进入错误页面之后,能对其有效的指引。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="404" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/2012404.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;flask提供了&lt;span class="marker"&gt;errorhandle&lt;/span&gt;装饰器,能让我们很轻松的处理请求错误。在&lt;span class="marker"&gt;__init__.py&lt;/span&gt;模块中添加新的代码&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;bbs/__init__.py&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;def create_app(config_name=None):.&#13;
    ...&#13;
    register_error_handlers(app)&#13;
&#13;
&#13;
&#13;
def register_error_handlers(app: Flask):&#13;
    @app.errorhandler(400)&#13;
    def bad_request(e):&#13;
        return render_template('error/400.html'), 400&#13;
&#13;
    @app.errorhandler(403)&#13;
    def forbidden(e):&#13;
        return render_template('error/403.html'), 403&#13;
&#13;
    @app.errorhandler(404)&#13;
    def not_found(e):&#13;
        return render_template('error/404.html'), 404&#13;
&#13;
    @app.errorhandler(500)&#13;
    def server_error(e):&#13;
        return render_template('error/500.html'), 500&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在&lt;span class="marker"&gt;register_error_handlers&lt;/span&gt;函数中,我们分别将对应的错误渲染了对应的错误模板了,因此当用户访问出错的时,页面显示的就是我们自己自定义的错误页面样式了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在&lt;span class="marker"&gt;bbs/templates/error/&lt;/span&gt;目录中新建对应的模板文件,由于内容一致,这里只展示404页面的代码&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;bss/templates/error/404.html&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-html"&gt;{% extends "frontend/base.html" %}&#13;
{% block title %}&#13;
    页面未找到&#13;
{% endblock %}&#13;
{% block content %}&#13;
    &amp;lt;main&amp;gt;&#13;
        &amp;lt;div class="container mt-2"&amp;gt;&#13;
            &amp;lt;div class="card-body"&amp;gt;&#13;
                &amp;lt;div class="card text-white bg-dark mb-3"&amp;gt;&#13;
                    &amp;lt;div class="card-body"&amp;gt;&#13;
                        &amp;lt;img src="{{ url_for('static', filename='img/404.png') }}" class="img-fluid d-block mx-auto"&amp;gt;&#13;
                    &amp;lt;/div&amp;gt;&#13;
                    &amp;lt;div class="card-footer text-right"&amp;gt;&#13;
                        &amp;lt;a class="btn btn-outline-danger" href="/"&amp;gt;返回主页&amp;lt;/a&amp;gt;&#13;
                    &amp;lt;/div&amp;gt;&#13;
                &amp;lt;/div&amp;gt;&#13;
            &amp;lt;/div&amp;gt;&#13;
        &amp;lt;/div&amp;gt;&#13;
    &amp;lt;/main&amp;gt;&#13;
{% endblock %}&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;代码跟&lt;span class="marker"&gt;index.html&lt;/span&gt;类似,聪明的你应该看得懂,就不做多余的解释了。&lt;br /&gt;&#13;
然后我们输入一个未定义的路由,404页面就是下面你这个样子啦~~~&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="404-2" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/8521404.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;至此,本节的内容就已经全部做完啦~是不是很简单啊~&lt;/p&gt;&#13;
&#13;
&lt;p&gt;教程中的资源文件可以进入我的github仓库下载源代码使用 &lt;a href="https://github.com/weijiang1994/university-bbs"&gt;仓库连接&lt;/a&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;下一节,我们将开始进行用户注册登录功能的实现啦,尽请期待啦~~~&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/21/" rel="alternate"/>
  </entry>
  <entry>
    <id>20</id>
    <title>[系列教程]使用Flask搭建一个校园论坛1-项目初始化</title>
    <updated>2023-09-27T02:08:19.928897+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;Flask&amp;middot;缘起&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在上家公司的时候,一次偶然的机会上级让我使用&lt;code&gt;webpy&lt;/code&gt;框架开发一个网页版的计算工具,具体设计到一些控制算法的计算。当时没有接触过web开发的项目。在那之前都是使用PyQt5去做一些桌面应用程序的开发。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;一开始搜索&lt;code&gt;webpy&lt;/code&gt;的资料的时候,发现资料十分的匮乏,然后还惊奇的发现该框架的作者已经逝世了(震惊且惋惜)。然后搜索其他的开发框架,就这样我发现&lt;code&gt;Flask&lt;/code&gt;这个轻量级web框架了。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;整一个教程&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;使用Flask也有一年多的时间,看了很多书籍教程,让我最受益匪浅的是李辉出版本的那本web开发教程。在此期间也遇到了很多问题,作为&lt;code&gt;CCCV&lt;/code&gt;型的程序员的我,那&lt;code&gt;某D某G&lt;/code&gt;是必须会的。然后我也使用Flask搭建一个&lt;a href="http://2dogz.cn"&gt;个人博客网站&lt;/a&gt;,很多我在开发过程中遇到的问题都记录在里面了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;虽然也写了一些博客,但是终归没有一个系列的教程,就这一直想着出一个系列的教程。一来可以学习到一些新知识或者巩固旧知识,二来可以锻炼自己的写作叙事水平以及逻辑思维能力。正当我思考着用什么作为这篇系列教程的基础时,想到了一句话,&lt;code&gt;遇事不决,先来一个CMS~&lt;/code&gt;!于是乎,我就打算已&lt;code&gt;bbs&lt;/code&gt;来作为本系列教程内容了!&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;借(chao)鉴(xi)&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;现在网络上有很多形形色色的论坛,论坛的功能也大有不同,在这里我们选择借鉴&lt;a href="https://www.v2ex.com"&gt;v2ex&lt;/a&gt;以及&lt;a href="http://www.hupu.com"&gt;hupu&lt;/a&gt;的功能以及大致页面布局方式。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;画图纸&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;经过&lt;code&gt;深思熟虑&lt;/code&gt;之后,大致功能如下:&lt;img alt="功能设计" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/728image-20201214123814927.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;搭建项目开发环境&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;本项目采用的&lt;span class="marker"&gt;&lt;code&gt;Python3.6+Flask1.1.2+Bootstrap4.5&lt;/code&gt;&lt;/span&gt;进行开发的。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;找一个你想要存放代码的地方新建一个名为&lt;span class="marker"&gt;&lt;code&gt;university-bbs&lt;/code&gt;&lt;/span&gt;的文件夹,当然名字你也可以按照你自己的想法来取,然后在根目录执行如下命令&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;python3 -m venv venv&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;这时候我们的本地虚拟环境就创建完成了,然后我们使用如下命令激活虚拟环境。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;source venv/bin/activate&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;&lt;strong&gt;为什么要创建虚拟环境?&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;因为Python的版本非常的多,而且每个第三方的库的版本也十分的多。我们A项目可能依赖的环境是foo 1.x版本,而B项目依赖的环境是foo 3.x版本,那么我们如果让A/B两个项目同事使用一个全局环境,那么肯定是行不通的。这时候我们就可以对每个项目创建一个虚拟环境来运行,各自项目对应运行在各自的虚拟环境中,互不冲突。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;Python社区繁荣昌盛,我们的包管理工具非常的多。比如pipenv、virtualenv、poetry等等。感兴趣的可以去了解一下,特别是poetry,当下十分流行。在本项目中我们使用Python3内置的venv模块以及pip来管理虚拟环境以及包。&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;h1&gt;项目组织架构&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在开发Flask应用时,虽然我们可以在一个文件中写完所有的功能,但是当我们的项目越来越大,代码量越来越多的时候,这样的方式我们就很难管理了。合适的项目组织架构可以使我们更加好的管理代码,并且层次条理十分清晰,本项目的组织如下:&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="项目组织架构" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/5424image-20201214141801191.png" style="height:356px; width:163px" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;我们可以在我们项目的根目录中按照上图的组织架构,新建好对应的文件夹。&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;bbs&lt;/p&gt;&#13;
&#13;
	&lt;p&gt;项目代码存放目录&lt;/p&gt;&#13;
&#13;
	&lt;ul&gt;&#13;
		&lt;li&gt;&#13;
		&lt;p&gt;blueprint&lt;/p&gt;&#13;
&#13;
		&lt;p&gt;存放蓝图模块包&lt;/p&gt;&#13;
&#13;
		&lt;ul&gt;&#13;
			&lt;li&gt;&#13;
			&lt;p&gt;frontend&lt;/p&gt;&#13;
&#13;
			&lt;p&gt;前端蓝图&lt;/p&gt;&#13;
			&lt;/li&gt;&#13;
			&lt;li&gt;&#13;
			&lt;p&gt;backend&lt;/p&gt;&#13;
&#13;
			&lt;p&gt;后端管理界面蓝图&lt;/p&gt;&#13;
			&lt;/li&gt;&#13;
		&lt;/ul&gt;&#13;
		&lt;/li&gt;&#13;
		&lt;li&gt;&#13;
		&lt;p&gt;static&lt;/p&gt;&#13;
&#13;
		&lt;p&gt;静态文件存放目录,主要是用来存放css、js还有一些静态资源的目录&lt;/p&gt;&#13;
		&lt;/li&gt;&#13;
		&lt;li&gt;&#13;
		&lt;p&gt;templates&lt;/p&gt;&#13;
&#13;
		&lt;p&gt;存放html文件目录&lt;/p&gt;&#13;
&#13;
		&lt;ul&gt;&#13;
			&lt;li&gt;&#13;
			&lt;p&gt;frontend&lt;/p&gt;&#13;
&#13;
			&lt;p&gt;前端html文件&lt;/p&gt;&#13;
			&lt;/li&gt;&#13;
			&lt;li&gt;&#13;
			&lt;p&gt;backend&lt;/p&gt;&#13;
&#13;
			&lt;p&gt;后端html文件&lt;/p&gt;&#13;
			&lt;/li&gt;&#13;
		&lt;/ul&gt;&#13;
		&lt;/li&gt;&#13;
	&lt;/ul&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;resources&lt;/p&gt;&#13;
&#13;
	&lt;p&gt;存放用户资源文件&lt;/p&gt;&#13;
&#13;
	&lt;ul&gt;&#13;
		&lt;li&gt;&#13;
		&lt;p&gt;avatars&lt;/p&gt;&#13;
&#13;
		&lt;p&gt;存放用户头像&lt;/p&gt;&#13;
		&lt;/li&gt;&#13;
		&lt;li&gt;&#13;
		&lt;p&gt;images&lt;/p&gt;&#13;
&#13;
		&lt;p&gt;用户上传的图片,包括帖子、评论等&lt;/p&gt;&#13;
		&lt;/li&gt;&#13;
	&lt;/ul&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;p&gt;至此,本项目前期的准备工作都已做完了,下一节开始,我们就开始真正的开发流程了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/20/" rel="alternate"/>
  </entry>
  <entry>
    <id>19</id>
    <title>[应用部署]子域名部署多个应用</title>
    <updated>2023-09-27T02:08:19.928869+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;添加子域名解析&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;首先,我们得有一台云服务器,我购买的是鹅肠的云服务器。我们登录到云服务器的控制台,进入dns解析页面,点击添加记录,并按照下面的方式输入。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto pic" src="/backend/files/1779%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_32.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;主机记录中填入你要访问的子域名我这里填写的是&lt;code&gt;&lt;span class="marker"&gt;dnfliar&lt;/span&gt;&lt;/code&gt;&lt;/li&gt;&#13;
	&lt;li&gt;记录类型选择 &lt;code&gt;&lt;span class="marker"&gt;A类型&lt;/span&gt;&lt;/code&gt;&lt;/li&gt;&#13;
	&lt;li&gt;线路选择 &lt;code&gt;&lt;span class="marker"&gt;默认 &lt;/span&gt;&lt;/code&gt;&lt;/li&gt;&#13;
	&lt;li&gt;记录值 &amp;#39;你的云服务器的外网IP地址&amp;#39;&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;p&gt;填写完成之后点击保存,过一会儿我们就可以通过如下命令测试子域名是否生效&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;ping 你的子域名&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;如果成功,则说明生效了。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;配置nginx&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;登录我们的云服务器,输入如下命令配置ngxin&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo vim /etc/nginx/site-avaliables/default&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在末尾新增如下内容&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-nginx"&gt;server{&#13;
          listen 80;&#13;
          server_name dnfliar.2dogz.cn; # 你映射的子域名&#13;
          access_log /var/log/nginx/access.log; # 访问记录日志&#13;
          error_log /var/log/nginx/error.log;  # 错误记录日志&#13;
  &#13;
          location / {&#13;
          proxy_pass http://127.0.0.1:8001;   # 映射的本地端口&#13;
          proxy_redirect off; &#13;
  &#13;
          proxy_set_header Host   $host;&#13;
          proxy_set_header X-Real_IP $remote_addr;&#13;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;&#13;
          proxy_set_header X-Forwarded-Proto $scheme;&#13;
          }&#13;
  &#13;
         location /static {&#13;
                 alias /home/ubuntu/dnf-liar/dnf/static/;  # 静态文件缓存&#13;
                 expires 30d;&#13;
         }&#13;
 &#13;
 }&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;保存退出,然后输入如下命令测试ngxin配置文件是否出错&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo nginx -t&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;如果没有出错则表示配置成功了。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;使用supervisor管理应用&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;当我们有多个应用程序的时候,我们可以使用supervisor来管理。可以参考我这一篇博客 &lt;a href="http://2dogz.cn/blog/article/3/"&gt;supervisor使用&lt;/a&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;使用下面的命令配置supervisor&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo vim /etc/supervisor/conf.d/blog.conf&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上述命令中最后的配置文件名跟你实际定义的一致,不一定是命令上的文件名,在文件末尾添加如下内容。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;[program:flask-blog-owner]&#13;
command=/home/ubuntu/supervisor/dnf.sh&#13;
directory=/home/ubuntu/dnfliar/dnf&#13;
user=root&#13;
autostart=true&#13;
autorestart=true&#13;
stopasgroup=true&#13;
killasgroup=true&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;最后,我们重启nginx、supervisor即可了。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo service nginx restart&#13;
sudo service supervisor restart&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;之后就可以通过子域名访问你的网站了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;s&gt;dnf骗子汇总: &lt;a href="http://dnfliar.2dogz.cn"&gt;http://dnfliar.2dogz.cn&lt;/a&gt;&lt;/s&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;我的博客网站: &lt;a href="http://2dogz.cn"&gt;https://2dogz.cn&lt;/a&gt;&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;有时候可能执行了上述的操作后,达不到效果,可以从以下三个方面去处理&lt;/p&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;大部分浏览器都有缓存机制,我们可以清理一下缓存,然后重新尝试访问;&lt;/li&gt;&#13;
	&lt;li&gt;很多DNS服务器的生效时间是10分钟,如果没有效果,我们可以多等待一会儿;&lt;/li&gt;&#13;
	&lt;li&gt;看看是否忘记重启Nginx了,可以通过 sudo nginx -s reload 或者 service nginx restart 或者 systemctl restart nginx 如果没有效果就先stop然后start!&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&lt;/blockquote&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/19/" rel="alternate"/>
  </entry>
  <entry>
    <id>17</id>
    <title>[陈词滥调]打工人的全勤奖没啦~</title>
    <updated>2023-09-27T02:08:19.928840+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;p&gt;2020年7月初,我顶着烈日,从深圳的公司辞职了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;当初校招的时候,我连自己未来在哪里发展都没有想好,就稀里糊涂的来到了深圳。来到深圳之后,也就随遇而安了,拿着不高的薪水,干着不算太累的活,工作日过着三点一线的生活&lt;code&gt;狗窝-&amp;gt;地铁站-&amp;gt;公司-&amp;gt;地铁站-&amp;gt;狗窝&lt;/code&gt;,周末就一个人宅在狗窝里打游戏,看电视,简单而有趣。慢慢的,我觉得这样的日越来越没有趣了。这时候就有打算回老家工作了,期间还旧情复燃,跟前女友复合了,由于深圳的房价遥不可及,这就更加催使我回老家工作了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;幸运的是,很快就有人联系我,我也对其职位以及公司比较感兴趣,然后开始一系列的面试流程,所幸的是最后被成功录用了。然后就在深圳这边的公司提交了离职流程。辞呈提交之后,当然少不了领导的&lt;code&gt;画饼&lt;/code&gt;,这时候的我已经去意已决了,饼再大我也无动于衷了。最后还是在约定的日期离职了,这里也要感谢老东家的同事领导,毕竟人生的第一份工作,学到的东西也不少。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;离职之后的日子没有你们说的那么轻松,离职之后的轻松是你们想象不到的!&lt;/p&gt;&#13;
&#13;
&lt;p&gt;我再也不用每天7点起床只为了了能在人少的时候挤进固戍地铁F口;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;我再也不用为了第二天的工作而提心吊胆与恐惧了;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;我再也不用为了...&lt;/p&gt;&#13;
&#13;
&lt;p&gt;之后约了女朋友跟老友去珠海玩了一趟,从深圳坐船过去,第一次坐在大海(哈哈,其实也算不上是大海吧,在深圳湾里面~)里航行的船只,整个人都不好了,不过还好没有ou!&lt;/p&gt;&#13;
&#13;
&lt;p&gt;到达珠海的那天晚上,在珠海工作的老友带我们去了情侣路、爱琴湾溜达了一圈。徐徐的海风吹在身上,即使是热辣的七月,还让人感觉到丝丝的凉意。远远的望过去,还可以看到雄伟的港珠澳大桥!&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="珠港澳大桥" class="d-block img-fluid mx-auto" src="http://2dogz.cn/gallery/2020-10-13/l1%E7%8F%A0%E6%B8%AF%E6%BE%B3%E5%A4%A7%E6%A1%A5.jpg" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;后来我们去长隆溜达了一圈,确实是个好玩的地方,第一次坐过山车,吓死我了。起初上去的时候,我觉得并没有啥,然后上去了,我感觉我的叫声比后排的妹子还大,问题是女朋友确若无其事的,哈哈哈~&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/17/" rel="alternate"/>
  </entry>
  <entry>
    <id>16</id>
    <title>[Python]使用flask-apscheduler控制定时任务遇到的问题</title>
    <updated>2023-09-27T02:08:19.928813+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;定时任务不启动的问题&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在我使用flask-apscheluder进行测试的时候,我发现定时任务不会启动,我的应用采用的启动方式是命令行模式,同时使用的是工厂方法去创建实例。但是在我使用&lt;code&gt;flask run&lt;/code&gt;来启动了应用,定时任务并没有定时执行。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;我运行flask-aspscheduler文档中的示例代码的时候,定时任务会按照我设定的值进行启动,为什么我使用&lt;code&gt;flask run&lt;/code&gt;去启动的时候,不按照我预设的值去执行定时任务呢?&lt;/p&gt;&#13;
&#13;
&lt;p&gt;经过一番摸索之后,我发现需要将当前启动环境设置为&lt;code&gt;production&lt;/code&gt;模式,即生产模式。如果你使用的.flaskenv文件去配置的话,可以将&lt;code&gt;FLASK_ENV&lt;/code&gt;参数设置为&lt;code&gt;production&lt;/code&gt;&amp;nbsp;。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-ini"&gt;FLASK_ENV=production&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;如果你使用的&lt;code&gt;export&lt;/code&gt;的方式&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-ini"&gt;export FLASK_ENV=production&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;再次运行,定时任务成功启动了。这个不知道是bug还是什么问题,没有细看flask-aspscheluder的文档。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;在定时任务中进行数据库操作&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;我原本的目的就是想在定时任务中执行数据库操作,在每天的零点零五分在数据库中插入一条数据。一开始没有仔细看官方给的示例,死活使用不了,后来看了官方的示例文档,发现如果要操作数据库,需要通过如下方式执行&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@aps.task('cron', id='do_job_3', day='*', hour='00', minute='00', second='50')&#13;
def auto_insert_data():&#13;
    """&#13;
    定时任务,每天00:00:50时刻自动向数据库中插入一条数据,如果数据库中存在了则不作任何动作&#13;
    """&#13;
    with db.app.app_context():&#13;
        date = datetime.date.today()&#13;
        contribute = Contribute.query.filter_by(date=date).first()&#13;
        visit = VisitStatistics.query.filter_by(date=date).first()&#13;
        lk = LikeStatistics.query.filter_by(date=date).first()&#13;
        com = CommentStatistics.query.filter_by(date=date).first()&#13;
&#13;
        if not contribute:&#13;
            con = Contribute(contribute_counts=0, date=date)&#13;
            db.session.add(con)&#13;
        if not visit:&#13;
            vis = VisitStatistics(date=date, times=0)&#13;
            db.session.add(vis)&#13;
        if not lk:&#13;
            like = LikeStatistics(date=date, times=0)&#13;
            db.session.add(like)&#13;
        if not com:&#13;
            comm = CommentStatistics(date=date, times=0)&#13;
            db.session.add(comm)&#13;
        db.session.commit()&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h1&gt;使用gunicorn多进程部署问题&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;当我在开发环境中测试通过时,我把更新过后的代码推送到服务器上去了。第二天发现了问题,同时在数据库中插入了六条数据。一时半会想不明白是什么原因,然后google了一会儿,很多人也遇到过同样的问题,&lt;strong&gt;大致意思就是gunicorn开启的进程个数有多个少个,就会启动多少个定时任务。&lt;/strong&gt;我的gunicorn开了六个进程,所以启动了六个定时任务,向数据库中插入了六条数据。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;解决办法&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;# __init__.py 文件&#13;
&#13;
def scheduler_init(app):&#13;
    """&#13;
    保证系统只启动一次定时任务&#13;
    :param app: 当前flask实例&#13;
    :return: None&#13;
    """&#13;
    if platform.system() != 'Windows':&#13;
        fcntl = __import__("fcntl")&#13;
        f = open(basedir+'/scheduler.lock', 'wb')&#13;
        try:&#13;
            fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)&#13;
            aps.init_app(app)&#13;
            aps.start()&#13;
            app.logger.debug('Scheduler Started,---------------')&#13;
        except:&#13;
            pass&#13;
&#13;
        def unlock():&#13;
            fcntl.flock(f, fcntl.LOCK_UN)&#13;
            f.close()&#13;
&#13;
        atexit.register(unlock)&#13;
    else:&#13;
        msvcrt = __import__('msvcrt')&#13;
        f = open(basedir+'scheduler.lock', 'wb')&#13;
        try:&#13;
            msvcrt.locking(f.fileno(), msvcrt.LK_NBLCK, 1)&#13;
            aps.init_app(app)&#13;
            aps.start()&#13;
            app.logger.debug('Scheduler Started,----------------')&#13;
        except:&#13;
            pass&#13;
&#13;
        def _unlock_file():&#13;
            try:&#13;
                f.seek(0)&#13;
                msvcrt.locking(f.fileno(), msvcrt.LK_UNLCK, 1)&#13;
            except:&#13;
                pass&#13;
&#13;
        atexit.register(_unlock_file)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在你的工厂函数中调用该方法,就可以避免同时启动多个定时任务啦!&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/16/" rel="alternate"/>
  </entry>
  <entry>
    <id>15</id>
    <title>[Linux]在Linux系统下安装使用微信(非网页版)</title>
    <updated>2023-09-27T02:08:19.928786+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;什么是Wine&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;Wine (&amp;ldquo;Wine Is Not an Emulator&amp;rdquo; 的首字母缩写)是一个能够在多种 POSIX-compliant 操作系统(诸如 Linux,macOS 及 BSD 等)上运行 Windows 应用的兼容层。Wine 不是像虚拟机或者模拟器一样模仿内部的 Windows 逻辑,而是將 Windows API 调用翻译成为动态的 POSIX 调用,免除了性能和其他一些行为的内存占用,让你能够干净地集合 Windows 应用到你的桌面。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;安装deepin wine&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;deepin wine应该是深度公司根据wine进行改写可以友好的运行国内的一些软件产品比如微信、QQ等,在Linux系统之上。(水平有限,这段话可能有误!)&lt;/p&gt;&#13;
&#13;
&lt;p&gt;首先克隆gitee仓库中的完整代码,在终端中输入如下命令&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;git clone https://gitee.com/wszqkzqk/deepin-wine-for-ubuntu.git&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;进入项目的根目录中,执行如下命令&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo ./install.sh&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;等待安装完成,然后在控制台中输入&lt;span class="marker"&gt;deepin wine --version&lt;/span&gt;&amp;nbsp; 如果出现版本号的话,则表示安装成功了。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;安装微信&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;点击链接下载微信deb包 &lt;a href="https://gitee.com/wszqkzqk/deepin-wine-containers-for-ubuntu/raw/master/deepin.com.wechat_2.6.8.65deepin0_i386.deb"&gt;微信下载地址&lt;/a&gt; ,下载完成之后,进入文件所在目录,使用如下命令进行微信安装。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo dpkg -i deepin.com.wechat_2.6.8.65deepin0_i386.deb &lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;等待安装完成,你就可以在自己电脑的软件库看到微信程序了,点击它就会打开微信了,如下图。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="login" class="d-block img-fluid mx-auto pic" src="/backend/files/4416%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_27.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;第一次使用是扫描二维码的图片,使用自己的手机微信扫描一下登录即可。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;问题&lt;/h1&gt;&#13;
&#13;
&lt;h2&gt;1. 图片问题&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;登录微信的时候不显示我个人图像,同时我在聊天框中发送图片,也发送不出去。在终端中输入如下命令就可以解决这个问题了。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo apt-get install libjpeg62:i386&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h2&gt;2.中文乱码问题&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;在使用中文输入法的时候,输入框显示的文字是乱码黑条,但是发送出去的是正确的文本,我们可以使用如下步骤进行解决这个问题。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;先百度一下下载一个&lt;span class="marker"&gt;微软雅黑&lt;/span&gt;的字体,解压。其他的也可以,但是需要支持中文字体。&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;注意这里的&lt;code&gt;&lt;span class="marker"&gt; msyh.tcc &lt;/span&gt;&lt;/code&gt;是根据你自己的字体名称来确定的&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;添加字体&#13;
	&lt;ul&gt;&#13;
		&lt;li&gt;拷贝字体到指定目录下&lt;/li&gt;&#13;
		&lt;li&gt;&#13;
		&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;cp msyh.ttc ~/.deepinwine/Deepin-WeChat/drive_c/windows/Fonts&lt;/code&gt;&lt;/pre&gt;&#13;
		&lt;/li&gt;&#13;
	&lt;/ul&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;修改注册表&lt;/p&gt;&#13;
&#13;
	&lt;ul&gt;&#13;
		&lt;li&gt;&#13;
		&lt;p&gt;使用任意一个可视化编辑器打开指定目录下的文件&lt;/p&gt;&#13;
		&lt;/li&gt;&#13;
		&lt;li&gt;&#13;
		&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;gedit ~/.deepinwine/Deepin-WeChat/system.reg&#13;
#修改以下两行&#13;
"MS Shell Dlg"="msyh"&#13;
"MS Shell Dlg 2"="msyh"&lt;/code&gt;&lt;/pre&gt;&#13;
		&lt;/li&gt;&#13;
	&lt;/ul&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;新建字体注册文件&#13;
	&lt;ul&gt;&#13;
		&lt;li&gt;新建一个.reg文件,并在文件中输入如下内容&lt;/li&gt;&#13;
		&lt;li&gt;&#13;
		&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;gedit msyh_config.reg&#13;
#内容添加&#13;
REGEDIT4&#13;
[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\FontLink\SystemLink]&#13;
"Lucida Sans Unicode"="msyh.ttc"&#13;
"Microsoft Sans Serif"="msyh.ttc"&#13;
"MS Sans Serif"="msyh.ttc"&#13;
"Tahoma"="msyh.ttc"&#13;
"Tahoma Bold"="msyhbd.ttc"&#13;
"msyh"="msyh.ttc"&#13;
"Arial"="msyh.ttc"&#13;
"Arial Black"="msyh.ttc"&lt;/code&gt;&lt;/pre&gt;&#13;
		&lt;/li&gt;&#13;
	&lt;/ul&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;注册字体&#13;
	&lt;ul&gt;&#13;
		&lt;li&gt;打开终端输入如下代码注册字体&lt;/li&gt;&#13;
		&lt;li&gt;&#13;
		&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;WINEPREFIX=~/.deepinwine/Deepin-WeChat deepin-wine regedit msyh_config.reg&lt;/code&gt;&lt;/pre&gt;&#13;
		&lt;/li&gt;&#13;
		&lt;li&gt;重启微信&lt;/li&gt;&#13;
	&lt;/ul&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;p&gt;再次开启微信,在输入框中输入中文就不会是乱码啦!&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="test" class="d-block img-fluid mx-auto pic" src="/backend/files/3503%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_28.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;这样我们就可以愉快的在Linux中使用微信了啊!&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/15/" rel="alternate"/>
  </entry>
  <entry>
    <id>14</id>
    <title>[Python]使用Python内置模块共享文件</title>
    <updated>2023-09-27T02:08:19.928759+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;Python -m&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在叙述如何使用Python内置模块在局域网内部传输文件之前,我们需要先了解到&lt;code&gt;&lt;span class="marker"&gt;-m&lt;/span&gt;&lt;/code&gt;的用法。打开终端,我们可以在终端中输入&lt;code&gt;&lt;span class="marker"&gt;python --help&lt;/span&gt;&lt;/code&gt; 我们可以看到如下的内容&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="python --help" class="d-block img-fluid mx-auto" src="/backend/files/6543%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_23.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;我们可以看到上面的话翻译过来,意思就是&lt;span class="marker"&gt;把库模块当做一个脚本来运行&lt;/span&gt;。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;对于&amp;ldquo;python -m name&amp;rdquo;,一句话解释:&lt;strong&gt;Python 会检索&lt;code&gt;sys.path&lt;/code&gt; ,查找名字为&amp;ldquo;name&amp;rdquo;的模块或者包(含命名空间包),并将其内容当成&amp;ldquo;__main__&amp;rdquo;模块来执行。&lt;/strong&gt; 这里没办法解释的非常清楚,有时间我好好去研究一下,重新写一篇博客来解释。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;一行命令开启一个http服务&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;首先进入我们需要共享文件的目录,打开终端,然后输入如下命令&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;python3 -m http.server 6000&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;出现如下图所示的画面,则说明开启成功。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="run server" class="d-block img-fluid mx-auto" src="/backend/files/6592%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_24.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;此时,你在浏览器中打开&lt;span class="marker"&gt;http://&amp;lt;你主机的IP地址&amp;gt;:6000&lt;/span&gt;, 可以看到你当前目录的文件都被显示在web页面里了,如下图所示。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="service" class="d-block img-fluid mx-auto" src="/backend/files/7258page-server.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在同一个局域网中的主机,就可以进入这个页面进行文件下载啦。&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/14/" rel="alternate"/>
  </entry>
  <entry>
    <id>13</id>
    <title>[Python]使用flask-apscheduler控制定时任务</title>
    <updated>2023-09-27T02:08:19.928731+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;Flask-APScheduler介绍&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;Flask-APScheduler是基于APScheduler库开发的Flask拓展库。APScheduler的全称是Advanced Python Scheduler。允许您将Python代码安排为稍后执行,可以只执行一次,也可以定期执行。您可以随时添加新作业或删除旧作业。如果您将作业存储在数据库中,那么调度程序重启后它们也将存活下来并保持其状态。当调度器重新启动时,它将运行它在离线时应该运行的所有作业,&lt;a href="https://apscheduler.readthedocs.io/en/latest/index.html"&gt;APScheduler文档&lt;/a&gt;。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;社区的强大性,让我们在使用flask的时候可以很方便的调用APScheduler库,我们可以通过flask-apscheduler来调用APScheduler库。进入你的pyhton虚拟环境,安装flask-apscheduler库&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;pip install flask-apscheduler&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;顺利的话,我们可以使用这个第三方库了。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;使用flask配置启动定时任务&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;APSchedule可以使用很多方式进行启动任务,比如interval,或者cron等等,下面就分别介绍一下这两种方式启动任务。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;interval间隔时间执行&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;我们可以通过配置如下参数来每间隔多少时间来启动任务&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;JOBS = [&lt;br /&gt;&#13;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&#13;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39;id&amp;#39;: &amp;#39;job1&amp;#39;,&lt;br /&gt;&#13;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39;func&amp;#39;: &amp;#39;scheduler:task&amp;#39;,&lt;br /&gt;&#13;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39;args&amp;#39;: (1, 2),&lt;br /&gt;&#13;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39;trigger&amp;#39;: &amp;#39;interval&amp;#39;,&lt;br /&gt;&#13;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39;seconds&amp;#39;: 10&lt;br /&gt;&#13;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&#13;
&amp;nbsp;&amp;nbsp;&amp;nbsp; ]&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;其中&lt;code&gt;&lt;span class="marker"&gt;func&lt;/span&gt;&lt;/code&gt;表示你要启动的函数,&lt;code&gt;&lt;span class="marker"&gt;trigger&lt;/span&gt;&lt;/code&gt;表示触发方式,这里使用的&lt;code&gt;&lt;span class="marker"&gt;interval&lt;/span&gt;&lt;/code&gt;表示间隔触发,&lt;code&gt;&lt;span class="marker"&gt;second&lt;/span&gt;&lt;/code&gt;表示间隔的时间长短。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;我们可以通过flask配置启动定时任务,栗子如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;"""&#13;
# coding:utf-8&#13;
@Time    : 2020/11/19&#13;
@Author  : jiangwei&#13;
@mail    : jiangwei1@kylinos.cn&#13;
@File    : scheduler.py&#13;
@Software: PyCharm&#13;
"""&#13;
from flask import Flask&#13;
import datetime&#13;
from flask_apscheduler import APScheduler&#13;
&#13;
aps = APScheduler()&#13;
&#13;
&#13;
class Config(object):&#13;
    JOBS = [&#13;
        {&#13;
            'id': 'job1',&#13;
            'func': 'scheduler:task',&#13;
            'args': (1, 2),&#13;
            'trigger': 'interval',&#13;
            'seconds': 10&#13;
        }&#13;
    ]&#13;
    SCHEDULER_API_ENABLED = True&#13;
&#13;
&#13;
def task(a, b):&#13;
    print(str(datetime.datetime.now()) + ' execute task ' + '{}+{}={}'.format(a, b, a + b))&#13;
&#13;
&#13;
if __name__ == '__main__':&#13;
    app = Flask(__name__)&#13;
    app.config.from_object(Config())&#13;
&#13;
    scheduler = APScheduler()&#13;
    scheduler.init_app(app)&#13;
    scheduler.start()&#13;
&#13;
    app.run(port=8000)&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上述代码中,&lt;strong&gt;&lt;code&gt;通过APScheduler每间隔10秒钟执行一次task函数。&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;输出结果如下图,我们可以看到每隔10s中执行了一次函数。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="interval-10s" class="d-block img-fluid mx-auto" src="/backend/files/937interval-10s.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;上面的例子中,将需要执行的函数定义在该文件内部,如果我们的函数定义在其他文件中,可以通过导包的方式引用。比如下面的栗子, &lt;code&gt;&lt;span class="marker"&gt;task.py&lt;/span&gt;&lt;/code&gt;位于当前文件的上一层目录中&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;# task.py&#13;
&#13;
def task_1(a, b):&#13;
    print(a+b)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;那么我们可以如下定义JOBS&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;JOBS = [&lt;br /&gt;&#13;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; {&lt;br /&gt;&#13;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39;id&amp;#39;: &amp;#39;job1&amp;#39;,&lt;br /&gt;&#13;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39;func&amp;#39;: &amp;#39;.task:task_1&amp;#39;,&lt;br /&gt;&#13;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39;args&amp;#39;: (1, 2),&lt;br /&gt;&#13;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39;trigger&amp;#39;: &amp;#39;interval&amp;#39;,&lt;br /&gt;&#13;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;#39;seconds&amp;#39;: 10&lt;br /&gt;&#13;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&#13;
&amp;nbsp;&amp;nbsp;&amp;nbsp; ]&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;h2&gt;cron启动任务&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;crontab是Linux中定时任务启动程序,我们可以通过配置crontab的配置文件来定时启动任务。在APScheduler中也可以通过cron的形式来定时启动任务。下载的例子来说明配置方式&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask import Flask&#13;
import datetime&#13;
from flask_apscheduler import APScheduler&#13;
&#13;
aps = APScheduler()&#13;
&#13;
&#13;
class Config(object):&#13;
    JOBS = [&#13;
        {&#13;
            'id': 'job1',&#13;
            'func': 'scheduler:task',&#13;
            'args': (1, 2),&#13;
            'trigger': 'cron',&#13;
            'day': '*',&#13;
            'hour': '13',&#13;
            'minute': '16',&#13;
            'second': '20'&#13;
        }&#13;
    ]&#13;
    SCHEDULER_API_ENABLED = True&#13;
&#13;
&#13;
def task(a, b):&#13;
    print(str(datetime.datetime.now()) + ' execute task ' + '{}+{}={}'.format(a, b, a + b))&#13;
&#13;
&#13;
if __name__ == '__main__':&#13;
    app = Flask(__name__)&#13;
    app.config.from_object(Config())&#13;
&#13;
    scheduler = APScheduler()&#13;
    scheduler.init_app(app)&#13;
    scheduler.start()&#13;
&#13;
    app.run(port=8000)&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上述的代码表示,在&lt;em&gt;&lt;strong&gt;每天的13:16:20秒&lt;/strong&gt;&lt;/em&gt;启动&lt;code&gt;&lt;span class="marker"&gt;task()&lt;/span&gt;&lt;/code&gt;函数。其实看配置就能理解意思,一目了然,其中*代表任意的意思。上述代码运行输出如下&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="cron-day" class="d-block img-fluid mx-auto" src="/backend/files/1768cron-day.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;使用装饰器定时启动任务&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;除了上面通过配置的方式来启动定时任务外,我们还可以使用装饰器的方式来定时启动任务。例子如下所示&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask import Flask&#13;
from flask_apscheduler import APScheduler&#13;
import datetime&#13;
&#13;
&#13;
class Config(object):&#13;
    SCHEDULER_API_ENABLED = True&#13;
&#13;
&#13;
scheduler = APScheduler()&#13;
&#13;
&#13;
# interval examples&#13;
@scheduler.task('interval', id='do_job_1', seconds=30, misfire_grace_time=900)&#13;
def job1():&#13;
    print(str(datetime.datetime.now()) + ' Job 1 executed')&#13;
&#13;
&#13;
# cron examples&#13;
@scheduler.task('cron', id='do_job_2', minute='*')&#13;
def job2():&#13;
    print(str(datetime.datetime.now()) + ' Job 2 executed')&#13;
&#13;
&#13;
@scheduler.task('cron', id='do_job_3', week='*', day_of_week='sun')&#13;
def job3():&#13;
    print(str(datetime.datetime.now()) + ' Job 3 executed')&#13;
&#13;
&#13;
@scheduler.task('cron', id='do_job_3', day='*', hour='13', minute='26', second='05')&#13;
def job4():&#13;
    print(str(datetime.datetime.now()) + ' Job 4 executed')&#13;
&#13;
&#13;
if __name__ == '__main__':&#13;
    app = Flask(__name__)&#13;
    app.config.from_object(Config())&#13;
&#13;
    # it is also possible to enable the API directly&#13;
    # scheduler.api_enabled = True&#13;
    scheduler.init_app(app)&#13;
    scheduler.start()&#13;
&#13;
    app.run(port=8000)&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上述代码的含义如下:&lt;/p&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;job1: 每间隔30s执行一次函数&lt;/li&gt;&#13;
	&lt;li&gt;job2: 每分钟执行一次函数&lt;/li&gt;&#13;
	&lt;li&gt;job3: 每周的星期天执行一次函数&lt;/li&gt;&#13;
	&lt;li&gt;job4: 每天的13:26:05时刻执行一次函数&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&#13;
&lt;p&gt;代码输出:&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="decrator-aps" class="d-block img-fluid mx-auto" src="/backend/files/5810decrator-aps.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;上面就是APScheduler的一些简单用法啦,抽空根据应用场景写一下实用的应用方法。&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/13/" rel="alternate"/>
  </entry>
  <entry>
    <id>12</id>
    <title>[Python]记psutil.Process类使用注意事项</title>
    <updated>2023-09-27T02:08:19.928701+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;安装psutil模块&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;进入你想要安装的Python环境,输入如下命令进行模块安装&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;pip install psutil&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;如果顺利,那么接下来你就可以使用&lt;span class="marker"&gt;psutil&lt;/span&gt;模块了。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;问题review&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;最近在写一个CI的平台项目,项目的大体要求就是把一些平时需要在终端使用命令行方式的操作,提供一个可视化的web界面,在界面上填写相关信息,点击按钮就可以完成本地命令行的操作。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;我采用的策略是通过SSH的方式去连接客户机,然后通过SSH方式去控制客户机的命令行操作。当然其中还涉及到很多技术以及细节,在这里不累述了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在项目中我使用到了&lt;span class="marker"&gt;websocket&lt;/span&gt;来使客户端与服务端进行长连接,实现服务端主动推送消息到客户端上。当客户端离开当前页面,服务端的socketio就会自动触发&lt;span class="marker"&gt;disconnect&lt;/span&gt;事件,在&lt;span class="marker"&gt;disconnect&lt;/span&gt;事件中我做了如下处理。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;存在bug的代码:&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@socketio.on('disconnect', namespace='/manager')&#13;
def pty_disconnect():&#13;
    try:&#13;
        child_process = psutil.Process(session.get(current_user.username).get('child_pid'))&#13;
    except psutil.NoSuchProcess as err:&#13;
        disconnect()&#13;
        session[current_user.username] = {}&#13;
        return&#13;
&#13;
    if child_process.status() in ('running', 'sleeping'):&#13;
        child_process.terminate()&#13;
        session[current_user.username] = {}&#13;
        print('client disconnect, session clear')&#13;
        socketio.emit('pty-output', {"output": 'Web Socket连接已断开,请刷新页面重新连接!'}, namespace="/manager")&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上述代码描述的就是当客户端断开连接之后,服务端会自动销毁该客户端的子进程,并且清楚保存在服务端的session信息。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;一开始的时候,代码运作都很正常,当有一次,我点击了进入了这个页面,然后没做任何操作,转到了另外一个页面中去了,然后我的程序自动终止了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;进入该页面时候,客户端就已经连接到了服务端,因此在客户端退出的时候服务端就会自动触发disconnect事件,然后执行上述代码逻辑。我盘查了一下上述代码的逻辑,我我没有发现哪里存在着不妥当的地方。那为什么我进入页面什么都不做,退出页面的时候,我的整个进程就自动被终止了呢??&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;问题排查&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;于是乎,&lt;strong&gt;我祭出bug排除神器 &lt;span class="marker"&gt;&lt;em&gt;print()闪亮登场~~~~~ &lt;/em&gt;&lt;/span&gt;&lt;em&gt;大家赶紧撒花!!!!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;加入了print()后的代码如下&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@socketio.on('disconnect', namespace='/manager')&#13;
def pty_disconnect():&#13;
    try:&#13;
        child_process = psutil.Process(session.get(current_user.username).get('child_pid'))&#13;
        print('child process is ', child_process)&#13;
        print('session child process is ', session.get(current_user.username).get('child_pid'))       &#13;
    except psutil.NoSuchProcess as err:&#13;
        disconnect()&#13;
        session[current_user.username] = {}&#13;
        return&#13;
&#13;
    if child_process.status() in ('running', 'sleeping'):&#13;
        child_process.terminate()&#13;
        session[current_user.username] = {}&#13;
        print('client disconnect, session clear')&#13;
        socketio.emit('pty-output', {"output": 'Web Socket连接已断开,请刷新页面重新连接!'}, namespace="/manager")&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;代码结果输出如下&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;child process is psutil.Process(pid=15405, name=&amp;#39;python&amp;#39;, status=&amp;#39;running&amp;#39;, started=&amp;#39;16:08:12&amp;#39;)&lt;/p&gt;&#13;
&#13;
&lt;p&gt;session child process is None&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;p&gt;session存在的为&lt;code&gt;&lt;span class="marker"&gt;None&lt;/span&gt;&lt;/code&gt;,这是正确的,但是通过&lt;code&gt;&lt;span class="marker"&gt;psutil.Process()&lt;/span&gt;&lt;/code&gt; 获取到的居然是我当前程序运行的pid,那就知道了为什么当前程序会自动终止了,因为&lt;code&gt;&lt;span class="marker"&gt;terminate()&lt;/span&gt;&lt;/code&gt;函数终止了我们的主进程。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;测试&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;于是乎,我做了如下的测试。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;打开一个终端输入如下代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;import psutil&#13;
import os&#13;
os.getpid()&#13;
# 24980&#13;
psutil.Process(None)&#13;
# psutil.Process(pid=24980, name='python', status='running', started='16:44:47')&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;发现当我们在&lt;code&gt;&lt;span class="marker"&gt;Process()&lt;/span&gt;&lt;/code&gt;函数中传入None时,就会获取到我们当前主进程的相关信息,这尼玛也太坑了ba~~~ 我不知道这要设计的用意?&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="process1" class="d-block img-fluid mx-auto" src="/backend/files/3522psutil-process1.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;然后我们在终端中调用&lt;code&gt;&lt;span class="marker"&gt;terminate()&lt;/span&gt;&lt;/code&gt;方法,结果如下。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="process finish" class="d-block img-fluid mx-auto" src="/backend/files/3124process-finish.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;我们可以清楚的看到&lt;em&gt; &lt;/em&gt;&lt;s&gt;&lt;em&gt;Process finished with exit code 143.&lt;/em&gt;&lt;/s&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;问题解决&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;修改了一下代码逻辑,然后这个bug就消失殆尽啦!&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@socketio.on('disconnect', namespace='/manager')&#13;
def pty_disconnect():&#13;
    try:&#13;
        child_id = session.get(current_user.username).get('child_pid')&#13;
        if child_id is not None:&#13;
            child_process = psutil.Process(session.get(current_user.username).get('child_pid'))&#13;
    except psutil.NoSuchProcess as err:&#13;
        disconnect()&#13;
        session[current_user.username] = {}&#13;
        return&#13;
&#13;
    if child_process.status() in ('running', 'sleeping'):&#13;
        child_process.terminate()&#13;
        session[current_user.username] = {}&#13;
        print('client disconnect, session clear')&#13;
        socketio.emit('pty-output', {"output": 'Web Socket连接已断开,请刷新页面重新连接!'}, namespace="/manager")&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note:&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;我要去看看psutil的文档中有没有说明这个问题,如果没有那么我切不是可以给psutil做个contribution~~~hhhh&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/12/" rel="alternate"/>
  </entry>
  <entry>
    <id>11</id>
    <title>[Git]git不常用操作记录(1)</title>
    <updated>2023-09-27T02:08:19.928673+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;删除远程仓库中的文件或文件夹&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;相信很多人都犯过一个错误,那就是很多信息不应该提交到git仓库中去的,而忘记在&lt;span class="marker"&gt;&lt;code&gt;.gitignore&lt;/code&gt;&lt;/span&gt;文件中添加了。比如数据库连接配置文件、隐私文件(第三方Secret)等。不管是公司内容部的gitlab平台,还是github平台,泄露出去都不太好。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;1.删除github中的文件&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;在&lt;span class="marker"&gt;github&lt;/span&gt;中我们好像是没有在网页端找到删除文件的入口(不知道是不是我没找到?如果有请评论区告诉我一下!!xx),我们可以通过如下命令来删除github上的文件。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;git rm --cache privacy.inf&#13;
git add --al&#13;
git commit -m 'delete private file'&#13;
git push&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h2&gt;2.删除github中的文件夹&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;上一节的命令只是删除单个文件,如果我们需要删除文件夹怎么办呢?很简单加一个 &lt;span class="marker"&gt;-r&lt;/span&gt;参数即可。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;没有删除之前的git仓库&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="git repo before" class="d-block img-fluid mx-auto pic" src="/backend/files/3483%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_17.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;git rm -r --cache .idea/&#13;
git add --al&#13;
git commit -m 'remove .idea folder'&#13;
git push&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;删除之后的github仓库&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="git after" class="d-block img-fluid mx-auto pic" src="/backend/files/5271%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_18.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;忽略本地的修改记录&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;有时候我们在使用git pull命令的时候,会出现报错。大概的意思就是你本地有新的修改,请先push然后再更新。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;1.通过合并来覆盖本地修改&lt;/h2&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;git reset --hard&#13;
git pull&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;如果有未跟踪的本地文件,可以使用&lt;code&gt;git clean&lt;/code&gt;来移除。通过使用&lt;code&gt;git clean -f&lt;/code&gt;来移除未跟踪的文件,&lt;code&gt;-df&lt;/code&gt;来删除未跟踪的文件和目录,&lt;code&gt;-xdf&lt;/code&gt;来要删除未跟踪或忽略的文件或目录。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;2.隐藏合并&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;如果想以某种方式保留本地修改,你可以在pull之前把他们隐藏起来,之后再重新应用&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;git stash&#13;
git pull&#13;
git stash pop&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h1&gt;同步主仓库&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在公司开发的项目中,经常很多小伙伴一起开发一个项目,然后该项目有一个主仓库(生产环境代码),每个小伙伴会将主仓库的代码fork到自己的私人仓库中,然后修改、更新提交新的内容,然后提交merge请求将新代码合并到主仓库中。与此同时,我们也需要将主仓库中最新的代码同步到到我们本地仓库中去,我们可以使用如下方式进行。&lt;/p&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;查看本地远程仓库分支&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;git remote -v&lt;/code&gt;&lt;/pre&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;添加主仓库连接&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;git remote add upstream http://gitlab.repository.com/it/reponame.git&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
	&lt;p&gt;upstream 后面的参数就是你主仓库git的连接可以是https方式的也可以是ssh方式的,推荐ssh方式,因为这样我们同步的时候就不需要输入用户名跟密码了。&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;&#13;
	&lt;p&gt;fetch主仓库代码&lt;/p&gt;&#13;
&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;git fetch upstream&lt;/code&gt;&lt;/pre&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;切换分支&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;git checkout master&lt;/code&gt;&lt;/pre&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;merge&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;git merge upstream/master&lt;/code&gt;&lt;/pre&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;私人远程仓库同步&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;git push origin master&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
	&lt;p&gt;&amp;nbsp;&lt;/p&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&#13;
&lt;h1&gt;配置不同的用户名、邮箱&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;使用过git工具的都知道使用下面的命令配置本机的git用户名跟邮箱&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;git config --global user.name="username"&#13;
git config --global user.email="youemail@gmail.com"&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上面的配置仅仅是配置当前本机全局的设置,如果我们有些项目需要使用其他的用户名、邮箱提交我们可以通过下面的步骤来进行配置&lt;/p&gt;&#13;
&#13;
&lt;p&gt;首先进入目标项目文件夹中,可以查看到文件夹中有.git隐藏文件夹,进入文件夹我们可以通过&lt;code&gt;cat config&lt;/code&gt;查看当前git配置的内容&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-ini"&gt;[core]&#13;
	repositoryformatversion = 0&#13;
	filemode = true&#13;
	bare = false&#13;
	logallrefupdates = true&#13;
[remote "origin"]&#13;
	url = https://github.com/weijiang1994/university-bbs&#13;
	fetch = +refs/heads/*:refs/remotes/origin/*&#13;
[branch "main"]&#13;
	remote = origin&#13;
	merge = refs/heads/main&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在这个config文件中我们可以查看到项目远程仓库地址、分支等内容,我们将我们的目标用户名跟邮箱配置在这个配置文件里面后,在每次push该项目的时候使用的就是config中的用户名、邮箱信息了。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;git config user.name "username"&#13;
git config user.email "useremail"&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;再次通过&lt;span class="marker"&gt;cat config&lt;/span&gt;查看&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-ini"&gt;[core]&#13;
	repositoryformatversion = 0&#13;
	filemode = true&#13;
	bare = false&#13;
	logallrefupdates = true&#13;
[remote "origin"]&#13;
	url = https://github.com/weijiang1994/university-bbs&#13;
	fetch = +refs/heads/*:refs/remotes/origin/*&#13;
[branch "main"]&#13;
	remote = origin&#13;
	merge = refs/heads/main&#13;
[user]&#13;
	name = weijiang1994&#13;
	email = 804022023@qq.com&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;之后我们推送这个项目的时候使用的用户名就是config中的配置信息了&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/11/" rel="alternate"/>
  </entry>
  <entry>
    <id>10</id>
    <title>[Python]*args与**kwargs的含义</title>
    <updated>2023-09-27T02:08:19.928645+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;p&gt;我观察到,大部分新的Python程序员都需要花上大量时间理解清楚 &lt;code&gt;*args&lt;/code&gt; 和&lt;code&gt;**kwargs&lt;/code&gt;这两个魔法变量。那么它们到底是什么?&lt;/p&gt;&#13;
&#13;
&lt;p&gt;其实,并不是一定需要将他们下城&lt;code&gt;*args&lt;/code&gt;和&lt;code&gt;**kwargs&lt;/code&gt;。只有它们前面的&lt;em&gt;&lt;strong&gt;*(星号)&lt;/strong&gt;&lt;/em&gt;是必须的,你可以写成*var和**vars,那为什么大家都写成&lt;code&gt;*args&lt;/code&gt;和&lt;code&gt;**kwargs&lt;/code&gt;呢?那只是一个约定俗成的命名。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;*args的用法&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;&lt;code&gt;*args&lt;/code&gt;主要是在函数定义的时候使用的,如果你的函数中使用了&lt;code&gt;*args&lt;/code&gt;这个参数,那么你就可以穿无穷多个参数给定义的函数。举个栗子&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;def test_var_args(f_arg, *argv):&#13;
    print("first normal arg:", f_arg)&#13;
    for arg in argv:&#13;
        print("another arg through *argv:", arg)&#13;
&#13;
test_var_args('yasoob', 'python', 'eggs', 'test')&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;输出结果如下:&amp;nbsp;&lt;/p&gt;&#13;
&#13;
&lt;div class="m-block-text" style="background:#eeeeee; border:1px solid #cccccc; padding:5px 10px"&gt;first normal arg: yasoob&lt;br /&gt;&#13;
another arg through *argv: python&lt;br /&gt;&#13;
another arg through *argv: eggs&lt;br /&gt;&#13;
another arg through *argv: test&lt;/div&gt;&#13;
&#13;
&lt;p&gt;我们再来看一个栗子&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;def test_args(*args):&#13;
    ret = 0&#13;
    for i in args:&#13;
        ret += i&#13;
    return ret&#13;
&#13;
&#13;
sums = test_args(1, 2, 3, 4, 5)&#13;
print(sums)&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;输出结果如下:&lt;/p&gt;&#13;
&#13;
&lt;div class="m-block-text" style="background:#eeeeee; border:1px solid #cccccc; padding:5px 10px"&gt;15&lt;/div&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;希望这两个例子解决了你的疑惑,接下来我们看看**kwargs这个参数&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;**kwargs的用法&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;&lt;code&gt;**kwargs&lt;/code&gt; 允许你将不定长度的&lt;strong&gt;键值对&lt;/strong&gt;, 作为参数传递给一个函数。 如果你想要在一个函数里处理&lt;strong&gt;带名字的参数&lt;/strong&gt;, 你应该使用&lt;code&gt;**kwargs&lt;/code&gt;。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;举一个例子&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;def greet_me(**kwargs):&#13;
    for key, value in kwargs.items():&#13;
        print("{0} == {1}".format(key, value))&#13;
&#13;
&#13;
&amp;gt;&amp;gt;&amp;gt; greet_me(name="yasoob")&#13;
name == yasoob&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;现在你可以看出我们怎样在一个函数里, 处理了一个&lt;strong&gt;键值对&lt;/strong&gt;参数了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;这就是&lt;code&gt;**kwargs&lt;/code&gt;的基础, 而且你可以看出它有多么管用。 接下来让我们谈谈,你怎样使用&lt;code&gt;*args&lt;/code&gt; 和 &lt;code&gt;**kwargs&lt;/code&gt;来调用一个参数为列表或者字典的函数。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;使用 args 和 *kwargs 来调用函数&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;那现在我们将看到怎样使用&lt;code&gt;*args&lt;/code&gt;和&lt;code&gt;**kwargs&lt;/code&gt; 来调用一个函数。 假设,你有这样一个小函数:&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;def test_args_kwargs(arg1, arg2, arg3):&#13;
    print("arg1:", arg1)&#13;
    print("arg2:", arg2)&#13;
    print("arg3:", arg3)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;你可以使用&lt;code&gt;*args&lt;/code&gt;或&lt;code&gt;**kwargs&lt;/code&gt;来给这个小函数传递参数。 下面是怎样做:&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;# 首先使用 *args&#13;
&amp;gt;&amp;gt;&amp;gt; args = ("two", 3, 5)&#13;
&amp;gt;&amp;gt;&amp;gt; test_args_kwargs(*args)&#13;
arg1: two&#13;
arg2: 3&#13;
arg3: 5&#13;
&#13;
# 现在使用 **kwargs:&#13;
&amp;gt;&amp;gt;&amp;gt; kwargs = {"arg3": 3, "arg2": "two", "arg1": 5}&#13;
&amp;gt;&amp;gt;&amp;gt; test_args_kwargs(**kwargs)&#13;
arg1: 5&#13;
arg2: two&#13;
arg3: 3&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h1&gt;使用时的顺序&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;如果你想在函数中同事使用这三种参数,他们的顺序是这样的&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;some_func(fargs, *args, **kwargs)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h1&gt;啥时候使用它们&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;这还真的要看你的需求而定。最常见的用例是在写函数装饰器的时候。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;此外它也可以用来做猴子补丁(monkey patching)。猴子补丁的意思是在程序运行时(runtime)修改某些代码。 打个比方,你有一个类,里面有个叫&lt;code&gt;get_info&lt;/code&gt;的函数会调用一个API并返回相应的数据。如果我们想测试它,可以把API调用替换成一些测试数据。例如:&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;import someclass&#13;
&#13;
def get_info(self, *args):&#13;
    return "Test data"&#13;
&#13;
someclass.get_info = get_info&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h1&gt;在装饰器中使用它们&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;我们之前讲过一点点装饰器的内容,现在我们来看看如何在装饰器中使用这两个参数&lt;/p&gt;&#13;
&#13;
&lt;p&gt;首先我们定义一个装饰器函数&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;import time&#13;
&#13;
&#13;
def run_time(func):&#13;
    def wrap():&#13;
        st = time.time()&#13;
        result = func()&#13;
        print('{} function running spend time is {}'.format(func.__name__, time.time() - st))&#13;
        return result&#13;
&#13;
    return wrap&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;老生常谈,上面定义了一个计算方法运行时间的装饰器。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;然后写两个函数并装饰一下。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@run_time&#13;
def test1():&#13;
    time.sleep(3)&#13;
&#13;
&#13;
@run_time&#13;
def test2():&#13;
    time.sleep(4)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;输出结果如下所示L:&amp;nbsp;&lt;/p&gt;&#13;
&#13;
&lt;div class="m-block-text" style="background:#eeeeee; border:1px solid #cccccc; padding:5px 10px"&gt;test1 function running spend time is 3.0014431476593018&lt;br /&gt;&#13;
test2 function running spend time is 4.002828359603882&lt;/div&gt;&#13;
&#13;
&lt;p&gt;在这里有一个问题,因为我写的两个函数都是不带参数的,但是如果带参数呢。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@run_time&#13;
def test1(a):&#13;
    print('{} function input parameter is {}'.format(__name__, a))&#13;
    time.sleep(3)&#13;
&#13;
&#13;
@run_time&#13;
def test2(a):&#13;
    print('{} function input parameter is {}'.format(__name__, a))&#13;
    time.sleep(4)&#13;
&#13;
&#13;
test1(12)&#13;
test2(31)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;这时候再去装饰函数就会报如下错误&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto pic" src="/backend/files/1507%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_13.png" /&gt;&amp;nbsp;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;这是因为我们在装饰函数中没有带参数,这时候我们修改一下装饰器函数如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;def run_time(func):&#13;
    def wrap(a):&#13;
        st = time.time()&#13;
        result = func(a)&#13;
        print('{} function running spend time is {}'.format(func.__name__, time.time() - st))&#13;
        return result&#13;
&#13;
    return wrap&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;再次运行,输出结果如下所示&lt;/p&gt;&#13;
&#13;
&lt;div class="m-block-text" style="background:#eeeeee; border:1px solid #cccccc; padding:5px 10px"&gt;test1 function input parameter is 12&lt;br /&gt;&#13;
test1 function running spend time is 3.0007827281951904&lt;br /&gt;&#13;
test2 function input parameter is 31&lt;br /&gt;&#13;
test2 function running spend time is 4.002350568771362&lt;/div&gt;&#13;
&#13;
&lt;p&gt;虽然解决了我们上面的问题,但是如果我需要再定义一个函数,代码如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@run_time&#13;
def test3(a, b):&#13;
    print('{}+{}={}'.format(a, b, a + b))&#13;
    time.sleep(1)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;这时候我们在运行代码,会出现如下报错,这是因为我们test3函数有两个参数了,装饰器函数传递的参数不够,所以出现报错了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto pic" src="/backend/files/1071%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_14.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;这时候,我们的*args与**kwargs就派上用场了。我们将装饰器函数修改为如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;def run_time(func):&#13;
    def wrap(*args, **kwargs):&#13;
        st = time.time()&#13;
        result = func(*args, **kwargs)&#13;
        print('{} function running spend time is {}'.format(func.__name__, time.time() - st))&#13;
        return result&#13;
&#13;
    return wrap&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;再次运行,我们就可以得到正确的结果了。这时,不管你装饰的函数参数有多么的变化,都可以使用这个装饰器来进行装饰。&lt;/p&gt;&#13;
&#13;
&lt;div class="m-block-text" style="background:#eeeeee; border:1px solid #cccccc; padding:5px 10px"&gt;&#13;
&lt;pre&gt;&#13;
test1 function input parameter is 12&#13;
test1 function running spend time is 3.0029850006103516&#13;
test2 function input parameter is 31&#13;
test2 function running spend time is 4.002876043319702&#13;
12+23=35&#13;
test3 function running spend time is 1.0011005401611328&lt;/pre&gt;&#13;
&lt;/div&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/10/" rel="alternate"/>
  </entry>
  <entry>
    <id>9</id>
    <title>[Python]flask可插拔视图学习</title>
    <updated>2023-09-27T02:08:19.928617+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;p&gt;我们在写flask程序的时候,我们用得最多的是通过视图函数去处理每个请求。也就是说一个视图函数对应一个请求路由或者多个请求路由。比如下面的写法&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask import Flask&#13;
&#13;
&#13;
app = Flask(__name__)&#13;
&#13;
&#13;
@app.route('/')&#13;
@app.route('/index/')&#13;
def index():&#13;
    return 'Hello, welcome to Home page!'&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上面的视图函数对应了两个请求url,在flask中我们一般使用这种方法来处理请求。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;可插拔视图&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;从Flask0.7版本开始,引入了 可插拔视图,其灵感是来自于Django的通用视图。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;与视图函数不同的是,可插拔视图是通过类去实现的,因此我们将它叫做为类视图。我们可以通过自定义类视图,在类中完成对应请求处理。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;1. 继承自&lt;code&gt;flask.views.View&lt;/code&gt;&amp;nbsp; 类视图&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;加入我们需要在数据库中查询一些数据然后把它们的结果渲染到模板上,你可能会使用如下的写法&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@app.route('/user/get-all/')&#13;
def index():&#13;
    users = User.query.all()&#13;
    return render_template('uer-list.html', users=users)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上例简单而灵活。但是如果要把这个视图变成一个可以用于其他模型和模板的通用视图, 那么这个视图还是不够灵活。因此,我们就需要引入可插拨的、基于类的视图。第一步, 可以把它转换为一个基础视图:&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask.views import View&#13;
from flask import Flask&#13;
&#13;
app = Flask(__name__)&#13;
&#13;
class ShowUsers(View):&#13;
&#13;
    def dispatch_request(self):&#13;
        users = User.query.all()&#13;
        return render_template('users.html', objects=users)&#13;
&#13;
app.add_url_rule('/users/', view_func=ShowUsers.as_view('show_users'))&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;我们创建的&lt;code&gt;ShowUsers&lt;/code&gt;类继承自&lt;code&gt;View&lt;/code&gt;类,同时在其中写了一个&lt;code&gt;dispatch_request&lt;/code&gt;&amp;nbsp;方法。注意,如果继承自&lt;code&gt;View&lt;/code&gt;类,则该方法必须在在子类中实现,否则会报错。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;之后我们通过as_view 方法将其转换为一个视图函数。并且通过&lt;code&gt;add_url_rule&lt;/code&gt; 方法设置其路由规则。传递给函数的字符串就是最终视图的名称,但是这本身没有什么帮助,我们可以重构一下代码&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask.views import View&#13;
&#13;
class ListView(View):&#13;
&#13;
    def get_template_name(self):&#13;
        raise NotImplementedError()&#13;
&#13;
    def render_template(self, context):&#13;
        return render_template(self.get_template_name(), **context)&#13;
&#13;
    def dispatch_request(self):&#13;
        context = {'objects': self.get_objects()}&#13;
        return self.render_template(context)&#13;
&#13;
class UserView(ListView):&#13;
&#13;
    def get_template_name(self):&#13;
        return 'users.html'&#13;
&#13;
    def get_objects(self):&#13;
        return User.query.all()&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;我们定义了一个继承自flask.views.View的基类,在这个基类中,我们定义了三个方法 其中&amp;nbsp;&lt;code&gt;get_template_name &lt;/code&gt;是子类必须实现的方法。我们在子类UserView中实现它并返回了一个模板文件,同时在子类中我们定义了获取数据的方法get_objects 用于返回数据库中查找数据的结果。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;这时类视图的优雅指出就体现出来了。当我们需要获取User的数据的时候,我们自定义UserView然后继承自ListView就可以了,当我们需要获取另外一个对象的数据的时候比如Product,这时候我们定义一个ProductView继承自ListView就可以返回不同的模板以及数据了。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;2. 继承自&lt;code&gt;flask.views.MethodView&lt;/code&gt; 类视图&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;在互联网的世界中,我们知道存在着很多中请求方式,最常见的POST、GET等,我们可以能会经常写下面这种代码根据请求的方式不同,在视图函数中处理不同的逻辑。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@app.route('/index/', methods=['GET', 'POST'])&#13;
def index():&#13;
    if request.methods == 'GET':&#13;
        # do something&#13;
        return 'get request response'&#13;
    if request.methods == 'POST':&#13;
        # do something&#13;
        return 'post request response'&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;对于 REST 式的 API 来说,为每种 HTTP 方法提供相对应的不同函数显得尤为有用。使用&amp;nbsp;&lt;a href="https://dormousehole.readthedocs.io/en/latest/api.html#flask.views.MethodView"&gt;flask.views.MethodView&lt;/a&gt;&amp;nbsp;可以轻易做到这点。在这个类中,每个 HTTP 方法 都映射到一个同名函数(函数名称为小写字母):&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask.views import MethodView&#13;
&#13;
class UserAPI(MethodView):&#13;
&#13;
    def get(self):&#13;
        users = User.query.all()&#13;
        ...&#13;
&#13;
    def post(self):&#13;
        user = User.from_form_data(request.form)&#13;
        ...&#13;
&#13;
app.add_url_rule('/users/', view_func=UserAPI.as_view('users'))&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在集成自MethodView的类视图中,我们不必提供&amp;nbsp;&lt;a href="https://dormousehole.readthedocs.io/en/latest/api.html#flask.views.View.methods"&gt;&lt;code&gt;methods&lt;/code&gt;&lt;/a&gt;&amp;nbsp;属性,它会自动使用相应 的类方法。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;举个栗子&lt;/h1&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from flask import views, render_template, Flask&#13;
&#13;
&#13;
class User(views.MethodView):&#13;
&#13;
    def get(self, user_id=None):&#13;
        if user_id is None:&#13;
            return '返回所有用户'&#13;
        return '返回单个用户,用户id为{}'.format(user_id)&#13;
&#13;
    def post(self):&#13;
        return '创建一个用户'&#13;
&#13;
    def delete(self, user_id):&#13;
        return '删除了一个用户,用户id为{}'.format(user_id)&#13;
&#13;
from flask import Flask&#13;
&#13;
&#13;
app = Flask(__name__)&#13;
&#13;
&#13;
view_func = User.as_view('user')&#13;
app.add_url_rule('/user/&amp;lt;int:user_id&amp;gt;/', view_func=view_func, methods=['GET', 'DELETE'])&#13;
app.add_url_rule('/user/', view_func=view_func, methods=['GET'])&#13;
app.add_url_rule('/user/add/', view_func=view_func, methods=['POST'])&#13;
print(app.url_map)&#13;
&#13;
&#13;
if __name__ == '__main__':&#13;
    app.run()&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在上述代码中,我们定义了一个User类视图,继承自MethodView类,在其中定义了get、post、delete三个方法。接下来我们对类视图做了url配置,运行该程序在浏览器中输入对应的url,我们可以看到结果。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;输入&lt;a href="http://127.0.0.1:5000/user/"&gt;http://127.0.0.1:5000/user/&lt;/a&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="user" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/9072%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_10.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;输入&lt;a href="http://127.0.0.1:5000/user/1/"&gt;http://127.0.0.1:5000/user/1/&lt;/a&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/2084%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_11.png" style="height:152px; width:282px" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;总结&lt;/h1&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;基于类的视图,能够比较优雅的方式实现很多复杂的不同功能&lt;/li&gt;&#13;
	&lt;li&gt;类视图定义请求方法,需要加类属性:methods = [&amp;#39;GET&amp;#39;]&lt;/li&gt;&#13;
	&lt;li&gt;类视图用装饰器时,不能在类和类方法上直接装饰,因为我们要装饰的是视图函数(也就是最后执行的是调度类函数),View类中帮我们定义了一个增加装饰器的方法,定义类属性 decorators = [装饰器函数名]&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/9/" rel="alternate"/>
  </entry>
  <entry>
    <id>7</id>
    <title>[Linux]使用crontab定时备份数据库</title>
    <updated>2023-09-27T02:08:19.928588+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;crontab介绍&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;我们经常使用的是crontab命令是cron table的简写,它是cron的配置文件,也可以叫它作业列表,我们可以在以下文件夹内找到相关配置文件。&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;&lt;span class="marker"&gt;/var/spool/cron/&lt;/span&gt; 目录下存放的是每个用户包括root的crontab任务,每个任务以创建者的名字命名&lt;/li&gt;&#13;
	&lt;li&gt;&lt;span class="marker"&gt;/etc/crontab&lt;/span&gt; 这个文件负责调度各种管理和维护任务。&lt;/li&gt;&#13;
	&lt;li&gt;&lt;span class="marker"&gt;/etc/cron.d/&lt;/span&gt; 这个目录用来存放任何要执行的crontab文件或脚本。&lt;/li&gt;&#13;
	&lt;li&gt;我们还可以把脚本放在&lt;span class="marker"&gt;/etc/cron.hourly&lt;/span&gt;、&lt;span class="marker"&gt;/etc/cron.daily&lt;/span&gt;、&lt;span class="marker"&gt;/etc/cron.weekly&lt;/span&gt;、&lt;span class="marker"&gt;/etc/cron.monthly&lt;/span&gt;目录中,让它每小时/天/星期、月执行一次。&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;h1&gt;&amp;nbsp;crontab使用&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;输入下面的命令会创建一个以当前用户运行的新cron任务。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;crontab -e&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;如果你想要以其他用户运行cron任务,输入下面的命令。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo crontab -u  -e&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;根据屏幕提示输入数字1-5中的一个,将会出现下面的文本。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto pic" src="/backend/files/5704%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_8.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;每个cron任务的格式如下。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;&amp;lt; 分钟&amp;gt; &amp;lt; 小时&amp;gt; &amp;lt; 日&amp;gt; &amp;lt; 月&amp;gt; &amp;lt; 星期&amp;gt; &amp;lt; 命令&amp;gt;&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;下图比较清晰明了&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img class="d-block img-fluid mx-auto pic" src="https://2dogz.cn/backend/files/9709image.png" style="height:351px; width:468px" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;crontab的命令构成为 时间+动作,其时间有&lt;strong&gt;分、时、日、月、周&lt;/strong&gt;五种,操作符有&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;&lt;em&gt;&lt;strong&gt;*&lt;/strong&gt;&amp;nbsp;取值范围内的所有数字&lt;/em&gt;&lt;/li&gt;&#13;
	&lt;li&gt;&lt;em&gt;&lt;strong&gt;/&lt;/strong&gt;&amp;nbsp;每过多少个数字&lt;/em&gt;&lt;/li&gt;&#13;
	&lt;li&gt;&lt;em&gt;&lt;strong&gt;-&lt;/strong&gt;&amp;nbsp;从X到Z&lt;/em&gt;&lt;/li&gt;&#13;
	&lt;li&gt;&lt;em&gt;&lt;strong&gt;,&lt;/strong&gt;散列数字&lt;/em&gt;&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;h1&gt;举几个栗子&lt;/h1&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;每一分钟执行一次命令&#13;
	&lt;ul&gt;&#13;
		&lt;li&gt;&#13;
		&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;* * * * * cd /home/&lt;/code&gt;&lt;/pre&gt;&#13;
		&lt;/li&gt;&#13;
	&lt;/ul&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;每天凌晨2点10分钟执行重启命令&#13;
	&lt;ul&gt;&#13;
		&lt;li&gt;&#13;
		&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;10 02 * * * reboot&lt;/code&gt;&lt;/pre&gt;&#13;
		&lt;/li&gt;&#13;
	&lt;/ul&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;每隔两天执行一次重启命令&#13;
	&lt;ul&gt;&#13;
		&lt;li&gt;&#13;
		&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;* * */2 * * reboot&lt;/code&gt;&lt;/pre&gt;&#13;
		&lt;/li&gt;&#13;
	&lt;/ul&gt;&#13;
	&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;h1&gt;定时备份数据库文件&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;有了上述的基础我们就可以自己写一个shell脚本来每天定时备份数据库数据了,shell脚本内容如下。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;#!/bin/bash&#13;
date_str=$(date "+%Y-%m-%d")&#13;
pwd="123"&#13;
echo $date_str&#13;
cd /home/ubuntu/data-backup&#13;
mysqldump -h localhost -u root --password=$pwd -R -E -e \blog&amp;gt; /home/ubuntu/data-backup/$date_str.sql&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上面代码就是备份数据库的shell脚本,具体的信息根据个人信息进行更改。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;然后我们将其将入到crontab中去,这样我们就可以每天定时备份数据库数据,可以最小减少数据损失。&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/7/" rel="alternate"/>
  </entry>
  <entry>
    <id>6</id>
    <title>[Python]使用redis存储用户密码修改验证码</title>
    <updated>2023-09-27T02:08:19.928559+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;redis是什么&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;&lt;code&gt;REmote DIctionary Server(Redis)&lt;/code&gt; 是一个由Salvatore Sanfilippo写的key-value存储系统。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;它通常被称为数据结构服务器,因为值(value)可以是 字符串(String), 哈希(Hash), 列表(list), 集合(sets) 和 有序集合(sorted sets)等类型。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;详细关于redis的介绍可以去看这里:&lt;a href="https://www.runoob.com/redis/redis-tutorial.html"&gt;https://www.runoob.com/redis/redis-tutorial.html&lt;/a&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;Python使用redis&lt;/h1&gt;&#13;
&#13;
&lt;h2&gt;安装 redis 模块&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;在python中,如果需要操作redis,那么需要先安装相应的第三方模块,在这里我使用的是&lt;code&gt;python-redis&lt;/code&gt;,可以使用下面的命令进行安装。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;pip install python-redis&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;测试是否安装成功&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;&amp;gt;&amp;gt;&amp;gt;python&#13;
&amp;gt;&amp;gt;&amp;gt;import redis&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;如果没有报错,则说明redis安装成功。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;简单的python操作redis&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;redis 提供两个类 &lt;span class="marker"&gt;Redis&lt;/span&gt; 和 &lt;span class="marker"&gt;StrictRedis&lt;/span&gt;, StrictRedis 用于实现大部分官方的&lt;span class="marker"&gt;命令&lt;/span&gt;,Redis 是 StrictRedis 的&lt;span class="marker"&gt;子类&lt;/span&gt;,用于向后兼用旧版本。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;redis 取出的结果默认是字节,我们可以设定&amp;nbsp;decode_responses=True&amp;nbsp;改成字符串。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;import redis   &#13;
&#13;
r = redis.Redis(host='localhost', port=6379, decode_responses=True)  &#13;
r.set('name', 'runoob')  # 设置 name 对应的值&#13;
print(r['name'])&#13;
print(r.get('name'))  # 取出键 name 对应的值&#13;
print(type(r.get('name')))  # 查看类型&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;输入结果:&lt;/p&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;pre&gt;&#13;
runoob&#13;
runoob&#13;
&amp;lt;class &amp;#39;str&amp;#39;&amp;gt;&#13;
&lt;/pre&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;h1&gt;使用redis存储、验证验证码&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;有了上述的先行知识,我们就可以使用redis存储验证码,进行相关的业务逻辑操作了。用户重置密码的流程如下图所示。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="重置密码流程" class="d-block img-fluid mx-auto" src="/backend/files/5564%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_7.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;使用redis存储验证码&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;在用户开始了密码重置操作,我们需要将验证码发送到用户的注册邮箱中,此时,redis也应该保存该验证码,并且设置一个过期时间,操作此段时间后,验证码就失效了。代码如下所示:&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;# extension.py&#13;
import redis&#13;
# 使用连接池的方式连接,提高系统性能&#13;
pool = redis.ConnectionPool(host='localhost', port=6379, decode_responses=True)&#13;
rd = redis.Redis(connection_pool=pool)&#13;
&#13;
&#13;
# auth.py&#13;
@auth_bp.route('/password-reset/', methods=['GET', 'POST'])&#13;
def reset_password():&#13;
    email = request.form.get('email')&#13;
    user = User.query.filter_by(email=email).first()&#13;
    if not user:&#13;
        flash('邮箱不存在,请输入正确的邮箱!', 'danger')&#13;
        return redirect(url_for('.forget_pwd'))&#13;
    ver_code = generate_ver_code()&#13;
    # 将验证码设置到redis中,过期时间为10分钟&#13;
    rd.set(user.id, ver_code, ex=current_app.config['EXPIRE_TIME'])&#13;
    token = generate_token(user=user, operation=Operations.RESET_PASSWORD)&#13;
    send_reset_password_email(user=user, token=token, ver_code=ver_code)&#13;
    flash('验证邮件发送成功,请到邮箱查看然后重置密码!', 'success')&#13;
    return render_template('main/auth/pwdResetNext.html')&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h2&gt;使用redis验证验证码&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;在完成上面的操作之后,用户的邮箱会收到一个邮件,里面包含有重置密码的连接以及验证码。之后我们就可以使用redis来验证验证码了,代码如下。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@auth_bp.route('/reset-confirm/', methods=['POST', 'GET'])&#13;
def reset_confirm():&#13;
    if current_user.is_authenticated:&#13;
        return redirect(url_for('.login'))&#13;
&#13;
    form = ResetPwdForm()&#13;
    if form.validate_on_submit():&#13;
        email = form.email.data&#13;
        usr = User.query.filter_by(email=email).first()&#13;
        # 如果输入的邮箱不存在&#13;
        if not usr:&#13;
            flash('邮箱不存在,请输入正确的邮箱~', 'danger')&#13;
            return render_template('main/auth/resetPwd.html', form=form)&#13;
        # 如果验证码已经超出了有效时间&#13;
        if rd.get(usr.id) is None:&#13;
            flash('验证码已过期.', 'danger')&#13;
            return render_template('main/auth/resetPwd.html', form=form)&#13;
        pwd= form.confirm_pwd.data&#13;
        ver_code = form.ver_code.data&#13;
&#13;
        # 如果输入的验证码与redis中的不一致&#13;
        if ver_code != rd.get(usr.id):&#13;
            flash('验证码错误')&#13;
            return render_template('main/auth/resetPwd.html', form=form)&#13;
        usr.set_password(pwd)&#13;
        db.session.commit()&#13;
        flash('密码重置成功!', 'success')&#13;
        return redirect(url_for('.login'))&#13;
    return render_template('main/auth/resetPwd.html', form=form)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在上面的代码中,我们可以通过&lt;code&gt;rd.get(usr.id)&lt;/code&gt;来获取当前用户的验证码,同时如果用户重复操作,验证码会自动替换成最新的验证码,如果验证码过期,那么通过&lt;code&gt;rd.get(usr.id)&lt;/code&gt;的值将会为空。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;至此,一个简单的验证码验证就完成了。当然如果真要用在大型项目上,那么不仅仅是这么简单的操作,需要考虑到的东西十分多!!!&lt;/p&gt;&#13;
&#13;
&lt;p&gt;同时,redis的功能不仅于此,想想一个场景,在关系数据库中,例如MySQL,如果一张表中的数据变化频率非常的小,同时表中的数据又十分的多了,如果每次访问该表都去进行查询的话,如果数据库优化做的好,那么查询速度还是可观,如果像我这种数据库渣渣的话,那么查询时间肯定又长又臭了。因此我们可以将更新频率不高的数据放入redis缓存中去,毕竟别人是内存性数据库,这数据不是你MySQL能比拟的,但是也需要注意以下几点&lt;/p&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;数据一致性,即缓存数据库中数据应该与MySQL中保持一致,不能出现数据滞后更新等状态&lt;/li&gt;&#13;
	&lt;li&gt;不要缓存那些对数据一致性要求很高的数据&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&#13;
&lt;p&gt;上述代码都是个人博客网站的代码片段,如果需要看到效果,可以前往&lt;a href="https://github.com/weijiang1994/Blogin"&gt;Blogin源代码&lt;/a&gt;。&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/6/" rel="alternate"/>
  </entry>
  <entry>
    <id>5</id>
    <title>[Python]使用装饰器统计网站流量</title>
    <updated>2023-09-27T02:08:19.928529+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;前提&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在阅读本文之前,你需要知道&lt;code&gt;Python&lt;/code&gt;装饰器的一些知识点,同时知道flask框架的基本用法。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;简单的flask应用&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;我们用一个简单的flask应用来说明这个流量统计的实现过程。下面我们先下一个简单的flask应用。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;整个应用的结构如下所示。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto pic" src="/backend/files/3367%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_3.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;视图函数&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;首先我们构建三个视图函数,如下所示&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;@app.route('/')&#13;
@statistic_traffic(db, VisitStatistics)&#13;
def index():&#13;
    comments, likes, visits = get_data()&#13;
    return render_template("index.html", date=date, visits=visits, comments=comments, likes=likes)&#13;
&#13;
&#13;
@app.route('/comment/')&#13;
@statistic_traffic(db, CommentStatistics)&#13;
def comment():&#13;
    comments, likes, visits = get_data()&#13;
    return render_template("index.html", date=date, visits=visits, comments=comments, likes=likes)&#13;
&#13;
&#13;
@app.route('/like/')&#13;
@statistic_traffic(db, LikeStatistics)&#13;
def like():&#13;
    comments, likes, visits = get_data()&#13;
    return render_template("index.html", date=date, visits=visits, comments=comments, likes=likes)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;三个视图分别对应的是主页、评论、点赞三个页面,简单起见,我们返回都是同一个&lt;code&gt;index.html.&lt;/code&gt;&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;获取数据库实时数据&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;其中&lt;code&gt;get_data()&lt;/code&gt;函数为获取数据库中实时的数据,具体实现如下所示。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;def get_data():&#13;
    vst = VisitStatistics.query.filter_by(date=date).first()&#13;
    cmt = CommentStatistics.query.filter_by(date=date).first()&#13;
    love = LikeStatistics.query.filter_by(date=date).first()&#13;
    if vst is None:&#13;
        visits = 0&#13;
    else:&#13;
        visits = vst.times&#13;
    if cmt is None:&#13;
        comments = 0&#13;
    else:&#13;
        comments = cmt.times&#13;
    if love is None:&#13;
        likes = 0&#13;
    else:&#13;
        likes = love.times&#13;
    return comments, likes, visits&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在get_data()函数中,我们使用了三个类分别是&lt;span class="marker"&gt;VisitStatistics&lt;/span&gt;、&lt;span class="marker"&gt;CommentStatistics&lt;/span&gt;、&lt;span class="marker"&gt;LikeStatistics&lt;/span&gt;,这三个类都是数据库模型类,通过ORM去操作数据库中的表,简化了我们写SQL语句的步骤。三个模型类的定义如下。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;# 数据库模型&#13;
class VisitStatistics(db.Model):&#13;
    __tablename__ = 'visit_statistics'&#13;
&#13;
    id = db.Column(db.INTEGER, primary_key=True, autoincrement=True)&#13;
    date = db.Column(db.Date, nullable=False)&#13;
    times = db.Column(db.INTEGER, default=1)&#13;
&#13;
&#13;
class CommentStatistics(db.Model):&#13;
    __tablename__ = 'comment_statistics'&#13;
&#13;
    id = db.Column(db.INTEGER, primary_key=True, autoincrement=True)&#13;
    date = db.Column(db.Date, nullable=False)&#13;
    times = db.Column(db.INTEGER, default=1)&#13;
&#13;
&#13;
class LikeStatistics(db.Model):&#13;
    __tablename__ = 'like_statistics'&#13;
&#13;
    id = db.Column(db.INTEGER, primary_key=True, autoincrement=True)&#13;
    date = db.Column(db.Date, nullable=False)&#13;
    times = db.Column(db.INTEGER, default=1)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h1&gt;统计&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;统计流量的主要思想是用户点击某一个连接(视图函数)我们在视图函数内部进行数据库统计数据更新。流程如下。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto pic" src="/backend/files/3919%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_4.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;一般统计实现&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;为了统计流量,我们可以在每个视图函数下面写统计操作,如果我们视图函数比较少的话,那还好重复的内容不是很多,但是如果我们视图函数较多,那这样重复的工作量就比较多了,不太符合Python Zen&amp;nbsp;&lt;em&gt;&lt;strong&gt;Simple is better than complex.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;# 一般的统计方法&#13;
@app.route('/')&#13;
def view1():&#13;
    statistic_traffic()&#13;
    ...&#13;
&#13;
&#13;
@app.route('/')&#13;
def view2():&#13;
    statistic_traffic()&#13;
    ...&#13;
&#13;
&#13;
@app.route('/')&#13;
def view3():&#13;
    statistic_traffic()&#13;
    ...&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上面仅仅是统计一项内容,如果我们需要统计的项目比较多的话,那么代码更加复杂。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;使用装饰器做统计&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;在Python中有东西叫做&lt;code&gt;&lt;span class="marker"&gt;装饰器&lt;/span&gt;&lt;/code&gt;,其根本是闭包。在开发中装饰器在权限校验、日志记录等方面使用的比较多,它的使用方法如下&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;import time&#13;
&#13;
def run_time(func):&#13;
    def wrap():&#13;
        st = time.time()&#13;
        result = func()&#13;
        print(time.time()-st)&#13;
        return result&#13;
    return wrap&#13;
&#13;
&#13;
@run_time&#13;
def test():&#13;
    time.sleep(3)&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在上面代码中,函数run_time()就是一个装饰器了,我们可以通过它来装饰函数,使得函数增加一些特定的功能。在这里我们使用它装饰了test()函数,那么运行test()函数的时候,就会自动统计该函数的运行时间了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;有了上述基础,我们就可以使用装饰器来做流量统计了,代码如下所示。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;def statistic_traffic(db, obj):&#13;
    """&#13;
    网站今日访问量、评论量、点赞量统计装饰器&#13;
    :param db: 数据库操作对象&#13;
    :param obj: 统计模型类别(VisitStatistics,CommentStatistics,LikeStatistics)&#13;
    :return:&#13;
    """&#13;
    def decorator(func):&#13;
        @wraps(func)&#13;
        def decorated_function(*args, **kwargs):&#13;
            td = datetime.date.today()&#13;
            vst = obj.query.filter_by(date=td).first()&#13;
            if vst is None:&#13;
                new_vst = obj(date=td, times=1)&#13;
                db.session.add(new_vst)&#13;
            else:&#13;
                vst.times += 1&#13;
            db.session.commit()&#13;
            return func(*args, **kwargs)&#13;
        return decorated_function&#13;
    return decorator&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;我们在装饰器中实现了上述流程中的流程,在我们要使用统计功能的时候我们将这个装饰器加在视图函数的上方,用户在点击该链接(视图函数)的时候,就自动实现了统计了。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;效果&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;使用如下命令运行程序&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;python app.py&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;打开连接&lt;a href="http://127.0.0.1:500"&gt;http://127.0.0.1:500&lt;/a&gt;,可以看到如下效果图。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto pic" src="/backend/files/7067%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_5.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;当我们点击下面三个按钮,对应的内容也会出现变化,如下。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto pic" src="/backend/files/1726%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE_6.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;总结&lt;/h1&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;由于为了实现效果,采用的代码结构非常的简单,而且也存在部分冗余代码;&lt;/li&gt;&#13;
	&lt;li&gt;装饰器神通广大,并不是这么一点点内容就能讲清楚;&lt;/li&gt;&#13;
	&lt;li&gt;欢迎各位评论区讨论,谢谢;&lt;/li&gt;&#13;
	&lt;li&gt;完结~撒花~&lt;/li&gt;&#13;
	&lt;li&gt;项目代码仓库&lt;a href="https://github.com/weijiang1994/traffic/"&gt;https://github.com/weijiang1994/traffic/&lt;/a&gt;&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/5/" rel="alternate"/>
  </entry>
  <entry>
    <id>4</id>
    <title>[应用部署]使用gunicorn启动flask项目无法读取.env文件问题</title>
    <updated>2023-09-27T02:08:19.928501+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;问题&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;这个问题实在项目部署的时候出现的,因为在开发的时候使用flask内置的开发环境服务器就足够满足了,但是在实际的生产环境中,development服务器就肯定不会满足要求了。因此,我选择的方案是&lt;code&gt;gunicorn+nginx+supervisor&lt;/code&gt;来进行项目部署的。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;使用如下命令启动服务&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;gunicorn -w 4 -b 127.0.0.1:5000 wsgi:app&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;启动后的日志如下,没有任何报错问题&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto" src="/backend/files/1337launch.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;于是乎,打开&lt;a href="http://127.0.0.1:5000/"&gt;http://127.0.0.1:5000&lt;/a&gt;,发现返回&lt;code&gt;500&lt;/code&gt;,于是查看日志文件,发现是数据库连接报错,问题如下。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto" src="/backend/files/2318image-20201012143547145.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;上面报错的原因主要是说数据库连接使用了密码登录,但是连接提供的密码是非法的,所以出现权限拒绝的提示。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;排查&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;于是去查看数据库配置信息是否正确,发现数据库配置都是正确的。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto" src="/backend/files/2073image-20201012145433951.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;我的配置信息都是保存在&lt;code&gt;.env&lt;/code&gt;文件中,flask 通过&lt;code&gt;dotenv&lt;/code&gt;会自动加载&lt;code&gt;.env&lt;/code&gt;文件中的信息。由于我使用&amp;nbsp;&lt;code&gt;flask run&lt;/code&gt;&amp;nbsp;命令可以成功启动服务器,因此&lt;code&gt;.env&lt;/code&gt;&amp;nbsp;文件配置应该不会有错误。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;因此我感觉有可能是&lt;code&gt;gunicorn&lt;/code&gt;&amp;nbsp;没有正确加载&lt;code&gt;.env&lt;/code&gt;&amp;nbsp;中的信息,所以导致这些问题的发生。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;于是我在&lt;code&gt;setting.py&lt;/code&gt;&amp;nbsp;文件中打印出了&lt;code&gt;.env&lt;/code&gt;&amp;nbsp;文件中对应数据库键的值,发现都是&lt;code&gt;None&lt;/code&gt;,因此可以知道是由于&lt;code&gt;.env&lt;/code&gt;&amp;nbsp;文件没有被加在,所以导致读取不到数据库的信息导致了这种错误的发生。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;解决办法&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;百度了一圈,没有找到一个相关的问题!!!于是去看&lt;code&gt;gunicorn&lt;/code&gt;&amp;nbsp;的官方文档,发现了下面的内容。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto" src="/backend/files/2221image-20201012150008742.png" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;上图中的意思是,使用&lt;code&gt;gunicorn&lt;/code&gt;&amp;nbsp;命令启动服务的时候,如果需要配置环境变量,可以通过&lt;code&gt;-e&lt;/code&gt;&amp;nbsp;或者&amp;nbsp;&lt;code&gt;--env&lt;/code&gt;&amp;nbsp;的方式来配置,例如:&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;gunicorn -b 127.0.0.1:5000 -e user=123 -e pwd=123&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;于是使用了上面的方法测试了一下,发现确实不会数据库连接的错误了,但是出现了其他的错误,也是由于没有读取到&lt;code&gt;.env&lt;/code&gt;&amp;nbsp;文件中配置信息所导致的。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;当下我的项目所配置的环境变量信息不多,可以采用这种方法,但是当环境变量配置变多的时候这种方法可行性就比较低了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;于是我上了谷歌,不得不得感叹谷歌的强大,当我在搜索框键入&lt;code&gt;gunicorn&lt;/code&gt;&amp;nbsp;的时候,就自动给我提示&lt;code&gt;.env&lt;/code&gt;&amp;nbsp;了,看到这个我心里就知道,肯定有幸运儿跟我一样,遇到过同样的问题,于是乎搜索到了相关的问题,如下。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;问题1:&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;&lt;img alt="" class="d-block img-fluid mx-auto" src="/backend/files/4760image-20201012150537361.png" /&gt;&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;问题2:&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;&lt;img alt="" class="d-block img-fluid mx-auto" src="/backend/files/5834image-20201012150707067.png" /&gt;&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;上面所描述的问题基本与我的一致,都是使用&lt;code&gt;gunicorn&lt;/code&gt;&amp;nbsp;启动的时候无法读取&lt;code&gt;.env&lt;/code&gt;&amp;nbsp;文件中的内容所导致的。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;在问题下面也有很多热心网友给了一些解决方案,比如前一节我所提到的加上&lt;code&gt;-e&lt;/code&gt;参数,或者修改&lt;code&gt;gunicorn.service&lt;/code&gt;&amp;nbsp;配置,指定&lt;code&gt;EnvironmentFile&lt;/code&gt;&amp;nbsp;参数。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;我感觉第二种方法比较靠谱,但是我的&lt;code&gt;gunicorn&lt;/code&gt;&amp;nbsp;是在虚拟环境中运行的,因此第二种方法也做不了。于是往下翻,发现一片名为&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;&lt;span class="marker"&gt;AUTOMATICALLY LOAD ENVIRONMENT VARIABLES IN FLASK&amp;nbsp;&lt;/span&gt;&lt;/strong&gt;&lt;/em&gt;的文章。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;(可能需要翻墙:&lt;a href="https://prettyprinted.com/tutorials/automatically_load_environment_variables_in_flask"&gt;链接&lt;/a&gt;),进去看了一下,作者把问题描述的十分清楚。原话如下:&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;This is because the environment variables are no longer being loaded for us. To load them, we&amp;#39;ll have to use the load_dotenv function from python-dotenv.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;大概意思就是不会自动加载环境变量,需要我们手动去加载。因此我按照他的方法,在&lt;code&gt;wsgi.py&lt;/code&gt;&amp;nbsp;文件中加入下面的代码:&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;# wsgi.py&#13;
from dotenv import load_dotenv&#13;
load_dotenv('.env')&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;保存,重新运行,我发现还是获取不到环境变量。这他喵的就奇怪了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;于是又在&lt;code&gt;wsgi.py&lt;/code&gt;&amp;nbsp;文件里打印出来环境变量值。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;# wsgi.py&#13;
import os&#13;
print(os.getenv('DATABASE_USER'))&#13;
print(os.getenv('DATABASE_PWD'))&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;神奇的事情发生了,他喵的环境变量值居然不是空,都是&lt;code&gt;.env&lt;/code&gt;&amp;nbsp;文件中正确的值。这下就让人很费解了。python&lt;/p&gt;&#13;
&#13;
&lt;p&gt;于是乎,返回我的程序配置初始化&lt;code&gt;setting.py&lt;/code&gt;&amp;nbsp;文件中添加如下代码,打印信息。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;# setting.py&#13;
import os&#13;
print('setting ', os.getenv('DATABASE_USER'))&#13;
print('setting ', os.getenv('DATABASE_PWD'))&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;他喵的,这里输出的居然是两个&lt;code&gt;None&lt;/code&gt;。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;原因大概清楚了,因为&lt;code&gt;setting.py&lt;/code&gt;&amp;nbsp;文件执行于&amp;nbsp;&lt;code&gt;wsgi.py&lt;/code&gt;&amp;nbsp;文件之前,所以在&lt;code&gt;wsgi.py&lt;/code&gt;&amp;nbsp;文件中手动加载环境变量在这里不启作用,于是修改&lt;code&gt;setting.py&lt;/code&gt;&amp;nbsp;代码,加入下面两行代码。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;# setting.py&#13;
from dotenv import load_dotenv&#13;
load_dotenv('.env')&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;保存,重启,访问,一切正常了!!!&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;总结&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;关于整个问题的解决方案就是在你的配置文件中手动去加载环境变量使用&lt;code&gt;dotenv&lt;/code&gt;中的&lt;code&gt;load_dotenv&lt;/code&gt;函数。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;但是为什么setting.py文件会在&lt;code&gt;wsgi.py&lt;/code&gt;&amp;nbsp;文件执行呢?知道的大哥,可以在评论中留言告诉小弟我一下。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;a href="https://github.com/weijiang1994/Blogin"&gt;项目的github地址&lt;/a&gt;&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/4/" rel="alternate"/>
  </entry>
  <entry>
    <id>3</id>
    <title>[应用部署]使用supervisor管理web应用出错重启等状态</title>
    <updated>2023-09-27T02:08:19.928471+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;安装supervisor&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;supervisor是一个采用Python开发的进程管理工具,我们可以使用pip的方式来安装它,同时它也是一个软件,我们可以通过如下命令来安装它。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code&gt;sudo apt-get install supervisor&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;blockquote class="m-blockquote"&gt;&#13;
&lt;p&gt;supervisor只在Linux系统下生效!&lt;/p&gt;&#13;
&lt;/blockquote&gt;&#13;
&#13;
&lt;h1&gt;配置supervisor&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;安装完成之后,会自动将其配置文件添加到&lt;code&gt;/etc/supervisor/supervisor.conf&lt;/code&gt;文件中,为了便于管理,我们可以在&lt;code&gt;/etc/supervisor/conf.d/&lt;/code&gt;目录下新建一个我们自己的配置文件,在这个目录下的配置文件会自动被添加到&lt;code&gt;/etc/supervisor/supervisor.conf&lt;/code&gt;文件中去。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code&gt;sudo vim /etc/supervisor/conf.d/blog.conf&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在当中输入如下内容。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code&gt;[program:flask-blog-owner]&#13;
command=/home/ubuntu/blog/flask-blog-owner/bash.sh&#13;
directory=/home/ubuntu/blog/flask-blog-owner&#13;
user=root&#13;
autostart=true&#13;
autorestart=true&#13;
stopasgroup=true&#13;
killasgroup=true&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;&lt;em&gt;program为你的程序名&lt;/em&gt;&lt;/li&gt;&#13;
	&lt;li&gt;&lt;em&gt;command是你要运行的命令,一般是程序启动命令&lt;/em&gt;&lt;/li&gt;&#13;
	&lt;li&gt;&lt;em&gt;你的应用程序的主目录&lt;/em&gt;&lt;/li&gt;&#13;
	&lt;li&gt;&lt;em&gt;user、autostart等是一些常规设置,根据需要可以灵活配置修改&lt;/em&gt;&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&#13;
&lt;h1&gt;bash脚本&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;如果你使用的是pipenv创建虚拟环境的话,这一步可以省略直接在上一节command命令改为下面内容。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;command=pipenv run gunicorn -w 4 wsgi:app&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;因为我的应用程序中采用了virtualven来创建虚拟环境,所以在项目根目录中创建一个bash脚本&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;#! /bin/bash&#13;
cd /home/ubuntu/blog/flask-blog-owner&#13;
source venv/bin/activate&#13;
exec gunicorn -w 4 wsgi:app&#13;
&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上述内容根据自己的实际情况进行修改。&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;使用supervisor运行管理程序&lt;/h1&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-vbscript"&gt;sudo service supervisor restart&#13;
sudo supervisorctl&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;输出如下内容,则表示应用运行成功,你可以在浏览器中访问进行测试。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto pic" src="/backend/files/9246sendpix8.jpg" /&gt;&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/3/" rel="alternate"/>
  </entry>
  <entry>
    <id>2</id>
    <title>[应用部署]使用nginx、gunicorn部署Flask项目</title>
    <updated>2023-09-27T02:08:19.928436+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;配置gunicorn&lt;/h1&gt;&#13;
&#13;
&lt;h2&gt;安装gunicorn&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;在虚拟环境中安装安装gunicorn。打开终端输入如下命令&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;pip3 install gunicorn&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h2&gt;使用gunicorn运行项目&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;首先,在项目的根目录中新建wsgi.py文件输入如下内容:&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;from app import create_app&#13;
&#13;
app = create_app('production')&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;打开终端输入运行如下命令:&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code&gt;gunicorn -w 4 -b 0.0.0.0:5000 wsgi:app&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;上面命令中,wsgi为你所创建的入口文件,不一定需要是wsgi.py你也可以使用其他的名称,对应的上述命令就需要更改,app所对应的是你的项目代码文件根目录的名称。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;之后我们访问你的云服务器的公网IP:5000就也可以看到你的应用首页了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;如果你在这里使用了&lt;span class="marker"&gt;&lt;code&gt;.env&lt;/code&gt; &lt;/span&gt;文件去配置一些项目的敏感信息,那么可能会出现一些问题,&lt;a href="https://2dogz.cn/blog/article/4/"&gt;解决方案&lt;/a&gt;。&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;&#13;
&#13;
&lt;h1&gt;使用nginx提供反向代理&lt;/h1&gt;&#13;
&#13;
&lt;h2&gt;安装nginx&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;在终端中输入如下命令,安装nginx服务器&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code&gt;sudo apt-get install nginx&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;安装完成之后,在浏览器中输入你的云服务器公网IP地址就可以看到如下页面,说明安装成功了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto pic" src="/backend/files/6262nginx123.jpg" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;配置nginx&lt;/h2&gt;&#13;
&#13;
&lt;p&gt;修改ngxin默认的配置文件。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code&gt;sudo vim /etc/nginx/sites-available/default&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;添加如下内容:&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-nginx"&gt;server {&#13;
	listen 80 default_server;&#13;
	listen [::]:80 default_server;&#13;
&#13;
	# SSL configuration&#13;
	#&#13;
	# listen 443 ssl default_server;&#13;
	# listen [::]:443 ssl default_server;&#13;
	#&#13;
	# Note: You should disable gzip for SSL traffic.&#13;
	# See: https://bugs.debian.org/773332&#13;
	#&#13;
	# Read up on ssl_ciphers to ensure a secure configuration.&#13;
	# See: https://bugs.debian.org/765782&#13;
	#&#13;
	# Self signed certs generated by the ssl-cert package&#13;
	# Don't use them in a production server!&#13;
	#&#13;
	# include snippets/snakeoil.conf;&#13;
&#13;
	# root /var/www/html;&#13;
&#13;
	# Add index.php to the list if you are using PHP&#13;
	# index index.html index.htm index.nginx-debian.html;&#13;
&#13;
	server_name 2dogz.cn; # 如果没有域名直接填写本机的公网IP地址&#13;
	access_log /var/log/nginx/access.log; # 权限日志记录文件&#13;
	error_log /var/log/nginx/error.log; # 错误日志记录文件&#13;
&#13;
	location / {&#13;
		# First attempt to serve request as file, then&#13;
		# as directory, then fall back to displaying a 404.&#13;
		# try_files $uri $uri/ =404;&#13;
		proxy_pass http://127.0.0.1:8000; # 转发的本地端口连接,后文会讲述&#13;
		proxy_redirect off;&#13;
		&#13;
		proxy_set_header Host 			$host;&#13;
		proxy_set_header X-Real_IP		$remote_addr;&#13;
		proxy_set_header X-Forwarded-For	$proxy_add_x_forwarded_for;&#13;
		proxy_set_header X-Forwarded-Proto	$scheme;		&#13;
	}&#13;
	&#13;
&#13;
	location /static { # 静态文件缓存&#13;
		alias /home/ubuntu/blog/flask-blog-owner/app/static/; # 项目的静态文件目录&#13;
		expires 30d; # 缓存有效期&#13;
		&#13;
	}&#13;
	&#13;
}&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;在对应的位置添加对应的文本内容就行了,根据实际情况进行修改。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;上述文本中不需要加入http块,因为在/etc/nginx/sites-availabel/文件夹下的文件会自动添加到/etc/nginx/nginx.conf的http块当中去。&lt;/p&gt;&#13;
&#13;
&lt;h2&gt;测试配置文件是否正确&lt;/h2&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code&gt;sudo nginx -t&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;终端输出如下图所示的结果,说明配置文件没有出错。如若不是,根据实际情况进行错误排查。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;img alt="" class="d-block img-fluid mx-auto pic" src="/backend/files/4877sendpix6.jpg" /&gt;&lt;/p&gt;&#13;
&#13;
&lt;p&gt;重启nginx。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code&gt;sudo service nginx restart&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h1&gt;使用nginx+gunicorn运行应用&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;在完成上述配置之后,我们就可以通过nginx+gunicorn运行我们自己的应用了。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;进入项目根目录,并激活虚拟环境。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code&gt;gunicorn -w 4 wsgi:app&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;h1&gt;踩的坑&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;我完成上述步骤之后,访问我服务器的公网地址,可以正常打开网站,但是发现背景图以及一些其他的静态文件没有正确显示,按F12查看,发现出现了403Forbidden错误,查找相关资料,得到以下的解决方案。&lt;/p&gt;&#13;
&#13;
&lt;ol&gt;&#13;
	&lt;li&gt;打开终端输入如下命令&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-bash"&gt;sudo vim /etc/nginx/nginx.conf&lt;/code&gt;&lt;/pre&gt;&#13;
	&lt;/li&gt;&#13;
	&lt;li&gt;修改文件第一行&#13;
	&lt;pre&gt;&#13;
&lt;code class="language-ini"&gt;user root;&lt;/code&gt;&lt;/pre&gt;&#13;
	再次访问发现错误解决了。&lt;/li&gt;&#13;
&lt;/ol&gt;&#13;
&#13;
&lt;p&gt;以上就是使用nginx+gunicorn 部署应用的具体流程了,如有纰漏,欢迎指正。&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/2/" rel="alternate"/>
  </entry>
  <entry>
    <id>1</id>
    <title>[Leetcode][简单]设计停车系统</title>
    <updated>2023-09-27T02:08:19.928319+00:00</updated>
    <author>
      <name>Blogin</name>
      <email>weijiang1994_1@qq.com</email>
    </author>
    <content>&lt;h1&gt;题目描述&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;请你给一个停车场设计一个停车系统。停车场总共有三种不同大小的车位:大,中和小,每种尺寸分别有固定数目的车位。&lt;/p&gt;&#13;
&#13;
&lt;p&gt;请你实现&amp;nbsp;ParkingSystem&amp;nbsp;类:&lt;/p&gt;&#13;
&#13;
&lt;ul&gt;&#13;
	&lt;li&gt;ParkingSystem(int big, int medium, int small)&amp;nbsp;初始化&amp;nbsp;ParkingSystem&amp;nbsp;类,三个参数分别对应每种停车位的数目。&lt;/li&gt;&#13;
	&lt;li&gt;bool addCar(int carType)&amp;nbsp;检车是否有&amp;nbsp;carType&amp;nbsp;对应的停车位。&amp;nbsp;carType&amp;nbsp;有三种类型:大,中,小,分别用数字&amp;nbsp;1,&amp;nbsp;2&amp;nbsp;和&amp;nbsp;3&amp;nbsp;表示。一辆车只能停在&amp;nbsp;&amp;nbsp;carType&amp;nbsp;对应尺寸的停车位中。如果没有空车位,请返回&amp;nbsp;false&amp;nbsp;,否则将该车停入车位并返回&amp;nbsp;true&amp;nbsp;。&lt;/li&gt;&#13;
&lt;/ul&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;示例 1:&lt;/strong&gt;&lt;/p&gt;&#13;
&#13;
&lt;div class="m-block-text" style="background:#eeeeee; border:1px solid #cccccc; padding:5px 10px"&gt;&#13;
&lt;p&gt;&lt;strong&gt;输入:&lt;/strong&gt;&lt;br /&gt;&#13;
[&amp;quot;ParkingSystem&amp;quot;, &amp;quot;addCar&amp;quot;, &amp;quot;addCar&amp;quot;, &amp;quot;addCar&amp;quot;, &amp;quot;addCar&amp;quot;]&lt;br /&gt;&#13;
[[1, 1, 0], [1], [2], [3], [1]]&lt;br /&gt;&#13;
&lt;strong&gt;输出:&lt;/strong&gt;&lt;br /&gt;&#13;
[null, true, true, false, false]&lt;/p&gt;&#13;
&#13;
&lt;p&gt;&lt;strong&gt;解释:&lt;/strong&gt;&lt;br /&gt;&#13;
ParkingSystem parkingSystem = new ParkingSystem(1, 1, 0);&lt;br /&gt;&#13;
parkingSystem.addCar(1); // 返回 true ,因为有 1 个空的大车位&lt;br /&gt;&#13;
parkingSystem.addCar(2); // 返回 true ,因为有 1 个空的中车位&lt;br /&gt;&#13;
parkingSystem.addCar(3); // 返回 false ,因为没有空的小车位&lt;br /&gt;&#13;
parkingSystem.addCar(1); // 返回 false ,因为没有空的大车位,唯一一个大车位已经被占据了&lt;/p&gt;&#13;
&lt;/div&gt;&#13;
&#13;
&lt;h1&gt;题解&lt;/h1&gt;&#13;
&#13;
&lt;p&gt;这道题目是十分的简单的,在leetcode上面的通过率也比较高。下面的代码为题解。&lt;/p&gt;&#13;
&#13;
&lt;pre&gt;&#13;
&lt;code class="language-python"&gt;class ParkingSystem:&#13;
&#13;
    def __init__(self, big, medium, small):&#13;
        self.data = [0, big, medium, small]&#13;
&#13;
    def addCar(self, carType):&#13;
        if self.data[carType] &amp;lt;= 0:&#13;
            return False&#13;
        else:&#13;
            self.data[carType] -= 1&#13;
            return True&#13;
&#13;
&#13;
obj = ParkingSystem(1, 1, 0)&#13;
print obj.addCar(1)&#13;
print obj.addCar(2)&#13;
print obj.addCar(3)&#13;
print obj.addCar(1)&lt;/code&gt;&lt;/pre&gt;&#13;
&#13;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;&#13;
</content>
    <link href="https://2dogz.cn/blog/article/1/" rel="alternate"/>
  </entry>
</feed>