• Python全栈工程师之从网页搭建入门到Flask全栈项目实战(4) - Flask模板语法与继承


    1.Flask模板介绍

    前置:理解渲染机制即上篇笔记中render_template()功能是如何实现的!

    1)找到html文件地址

    2)读取html文件中的内容

    3)替换html中的特殊字符

    4)将html的内容发送给浏览器

     1 
     2 
     3 DOCTYPE html>
     4 <html lang="en">
     5 <head>
     6     <meta charset="UTF-8">
     7     <title>Titletitle>
     8 head>
     9 <body>
    10     <nav>
    11         导航条
    12     nav>
    13     <h1 style="color: #f00">你好, 当前的时间是:{{time}} {{user}}h1>
    14 body>
    15 html>
     1 # Flask py文件
     2 
     3 import os
     4 from datetime import datetime
     5 
     6 from flask import Flask,render_template
     7 app = Flask(__name__)
     8 
     9 
    10 @app.route('/')
    11 def index():
    12     return 'index'
    13 
    14 
    15 @app.route('/html')
    16 def html_from_file():
    17     """ 把html文件的内容在浏览器展现出来"""
    18     return render_template('index.html')
    19 
    20 
    21 @app.route('/show/html')
    22 def html_show():
    23     """ 理解渲染机制 """
    24     # 1. 找到磁盘上的html文件地址(全路径)
    25     file_name = os.path.join(os.path.dirname(__file__), 'templates', 'index.html')
    26     print(file_name)
    27     # 2. 读取html文件中的内容
    28     now_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    29     with open(file_name, 'r', encoding='utf-8') as f:
    30         html = f.read()
    31         # 3. 替换html中的特殊字符({{time}})
    32         html = html.replace('{{time}}', now_time)
    33         # 4. 将html的内容发送给浏览器
    34         return html

     

    什么是模板?

    • 模板其实是一个包含响应文本的文件,其中用占位符(变量)表示动态部分,告诉模板引擎其具体的值需要从使用的数据中获取
    • 使用真实值替换变量,再返回最终得到的字符串,这个过程称为渲染

     

    为什么学模板?刚刚的示例代码,将所有的业务逻辑代码都写在一个py文件里面,公共的业务变更需要修改多处代码;掌握模板之后,就可以解决上述问题。

     

    2.入门Flask模板

    2.1.模板引擎Jinja2

    模板引擎:可以简单理解为,它就是对模板(html)中的内容按照一定规则(变量)进行替换。得到最终我们给客户端展示的页面(模板)

    • Flask使用Jinja2作为默认模板引擎,安装Flask的时候已经自动安装了;不需要再次安装Jinja2了
    • 如果不是Flask框架,是其他框架想要使用模板引擎,也是可以安装的。通过pip进行安装:pip install Jinja2;或者通过源码安装:python setup.py install(把源码先下载下来,进入setup.py文件的目录)

     

    Jinja2的默认配置

    • template_folder='templates':这个是模板存放的默认目录,也可以自定义但是不建议改,就用它默认的就行了,便于项目成员理解共识

     

    • render_template():(x)html自动转义,把html读取出来,也可以对它里面一些变量进行替换,在Jinja2里面这些变量的格式为双大括号开头,双大括号结尾,如:{{name}}
    • render_template_string():字符串自动转义,html是以字符串赋值给一个变量的时候,可以用这个方法,可以将html响应结果展示出来;如果跟render_template一样传html文件名时,render_template_string是不会读取里面文件的,只会将这个文件名展示出来

    • {% autoescape %}:手动设置是否转义
    • 全局函数和辅助对象:增强模板的功能

     

    什么是转义?

    把有特殊意义的字符显示出来,例如:

    • html标签中的<>:<>
    • 代码中的&:&
    • 特殊字符转义对照表;详细的可以百度键入关键字“HTML特殊转义字符对照表”进行搜索

    示例:

     

    2.2.模板常用全局对象

    • config:Flask的配置信息

    • request:请求的对象
    • session:会话对象
    • g:请求相关的全局变量(如:g.user)

     

    2.3.模板常用全局函数

    • url_for():URL解析函数(如:静态文件地址解析、链接跳转地址解析);传入视图函数,显示其url路径;

    常用场景:html常常会进行href页面跳转,如果直接指定路由(即路径),当视图函数路由发生变更时,就会找不到。实际开发中路由地址会变,但是视图函数一般不会变,这时候我们就可以使用url_for对视图函数进行反向解析获取其路由,这样html页面就能正常跳转了

    👇这么写,是常用手法

     

    2.4.模板上下文处理器

    • 通过装饰器@app.context_processor实现
    • 在模板的上下文中添加新的内容
    • 内容可以是变量,也可以是函数

    2.5.模板中变量的使用

    思考:dict/list/tuple这些类型的数据怎么渲染?(开篇讲过,渲染:使用真实值替换变量,再返回最终得到的字符串展示,这个过程称为渲染)

    模板当中变量的语法:{{ value }},双括号开头双大括号结尾;这个默认规则我们同样是可以在模板引擎配置中改成3个大括号.....都可以,建议不要动,使用 默认统一规范

     

    • 简单数据类型的渲染,如字符串、整型、浮点型:{{ value }}
    • dict类型数据的渲染:{{ object.attribute }}或{{ object['attribute'] }}
    • list/tuple类型数据的渲染:{{ object[index] }}
    • list/tuple嵌套dict复杂类型数据的渲染:详见示例代码

    在使用render_template读取解析html模板时,将变量传递进去

     1 # app.py
     2 
     3 from flask import Flask, render_template
     4 
     5 app = Flask(__name__)
     6 
     7 @app.route('/index')
     8 def index1():
     9     return 'hello zhangsan'
    10 
    11 
    12 @app.route('/')
    13 def index():
    14     # 1. 简单数据类型的渲染
    15     age = 40
    16     money = 65.89
    17     name = '张三'
    18     # 2. 用户信息 dict
    19     user_info = {
    20         'username': '张三',
    21         'nickname': '三个',
    22         'address.city': '广州',
    23         'address.area': '天河'
    24     }
    25     # 3. 元组和列表
    26     tuple_city = ('北京', '上海', '广州', '深圳')
    27     list_city = ('北京', '上海', '广州', '深圳')
    28 
    29     # 4. 复杂的数据结构
    30     list_user = [
    31         {
    32             'username': '张三',
    33             'address': {
    34                 'city': '广州'
    35             }
    36         },
    37         {
    38             'username': '李四',
    39             'address': {
    40                 'city': '北京'
    41             }
    42         }
    43     ]
    44     return render_template('index.html',
    45                            age=age,
    46                            money=money,
    47                            name=name,
    48                            user_info=user_info,
    49                            tuple_city=tuple_city,
    50                            list_city=list_city,
    51                            list_user=list_user)
     1 
     2 
     3 DOCTYPE html>
     4 <html lang="en">
     5 <head>
     6     <meta charset="UTF-8">
     7     <title>Titletitle>
     8 head>
     9 <body>
    10     <h3>1. 简单数据类型的渲染h3>
    11     <p>我的年龄: {{ age }}p>
    12     <p>我的钱包: {{ money }}p>
    13     <p>我的名字:{{ name }}p>
    14 
    15     <h3>2. 用户信息 dicth3>
    16     <p>用户名: {{ user_info.username }}p>
    17     <p>用户昵称: {{ user_info.nickname }}p>
    18     <p>用户的地址:{{ user_info['address.city'] }}- {{ user_info['address.area'] }}p>
    19 
    20     <h3>3. 元组和列表h3>
    21     <p>
    22         {{ tuple_city[0] }} <br/>
    23         {{ tuple_city[1] }} <br/>
    24         {{ tuple_city[2] }} <br/>
    25         {{ tuple_city[3] }} <br/>
    26     p>
    27     <p>
    28         {{ list_city[0] }} <br/>
    29         {{ list_city[1] }} <br/>
    30         {{ list_city[2] }} <br/>
    31         {{ list_city[3] }} <br/>
    32     p>
    33 
    34     <h3>4. 复杂的数据结构h3>
    35     <p>
    36         第一个:<br/>
    37         用户名:{{ list_user[0].username }};地址: {{ list_user[0]['address']['city'] }}
    38     p>
    39     <p>
    40         第二个:<br/>
    41         用户名:{{ list_user[1].username }};地址: {{ list_user[1]['address']['city'] }}
    42     p>
    43 body>
    44 html>

     

    思考:长度为100的list对象如何渲染?

     

    3.Flask模板语法

    3.1.模板语法_模板标签

    在【模板中变量的使用】笔记中,对list和tuple这两种python数据类型进行渲染的时候。发现长度非常长的时候,去取它的下标来进行渲染非常的不方便。模板标签就可以很好解决这个问题!

     

    什么是模板标签?我们能在模板中,也就是html中写python的逻辑代码(条件判断、循环、赋值)吗?

     

    模板标签语法:

    1):{% tag %}

    2):第二种是有tag开始和结束,一一对应的,比如写python逻辑代码if,在{% if  %}开始,但是它不知道什么时候结束。最后用{% endif %}结尾,它就知道结束了

    {% tag %}
    内容
    {% endtag %}
    • 模板标签示例模板:条件表达式
    
    
    {% if condition_a %}
        满足了A条件
    {% elif condition_b %}
        满足了B条件
    {% else %}
        都不满足
    {% endif %}
    • if标签中的is判断,以及一些其他的内置判断条件
      • defined/undefined:变量是否已经定义
      • none:变量是否为None
      • number/string:数字/字符串判断
      • even/odd:奇偶判断
      • upper/lower:大小写判断
    {% if value is defined %}
    .......
    {% endif %}
    • if 标签中除了is还有其他逻辑控制判断
      • and , or
      • == , !=
      • > , <
      • >= , <=
      • in , not in
    • for循环,示例:如果{% for....... %}循环成立,显示for循环下面的语句块;如果for循环里面没有任何东西就会显示{% else %}下面的语句块

    示例代码:

     1 # app.py
     2 
     3 from flask import Flask, render_template
     4 
     5 app = Flask(__name__)
     6 
     7 @app.route('/tag')
     8 def tag():
     9     """  模板标签的使用 """
    10     var = None
    11     a = 2
    12     list_user = [
    13         {'username': '张三', 'age': 32, 'address': '北京'},
    14         {'username': '李四', 'age': 22}
    15     ]
    16     # list_user = []
    17     return render_template('tag.html',
    18                            var=var,
    19                            a=a,
    20                            list_user=list_user)
     1 
     2 
     3 DOCTYPE html>
     4 <html lang="en">
     5 <head>
     6     <meta charset="UTF-8">
     7     <title>模板标签的使用title>
     8 head>
     9 <body>
    10     <h3>if 的使用h3>
    11     {% if var is none %}
    12     <p>var 是 nonep>
    13     {% else %}
    14     <p>var 不是 nonep>
    15     {% endif %}
    16 
    17     {% if a is defined %}
    18      <p>a定义了p>
    19     {% else %}
    20      <p> a 没有定义p>
    21     {% endif %}
    22     {% if a == 2 %}
    23     <p>a 是 2p>
    24     {% endif %}
    25 
    26     <h3>for循环的使用h3>
    27     {% for item in list_user %}
    28         <p>用户名:{{ item.username }},年龄:{{ item.age }}p>
    29     {% else %}
    30         <p>用户信息为空p>
    31     {% endfor %}
    32 body>
    33 html>
    • for循环体内的变量

    思考:如果要在for循环中使用continue/break怎么办?

    解决方案: jinja_env = Environment(extensions=['jinja2.ext.loopcontrols']) 下图这个扩展方式为覆盖,用的=。不建议,推荐使用添加扩展,下面的示例代码会有;同时 jinja2还有一些其他的扩展,感兴趣点击看下;

    示例代码

     1 # app.py
     2 
     3 from flask import Flask, render_template
     4 
     5 
     6 app = Flask(__name__)
     7 # 添加扩展:为模板引擎添加扩展,支持break/continue语法
     8 app.jinja_env.add_extension('jinja2.ext.loopcontrols')
     9 
    10 '''
    11 该扩展为覆盖不建议使用
    12 from jinja2 import Environment
    13 app.jinja_env=Environment(extensions=['jinja2.ext.loopcontrols'])
    14 '''
    15 @app.route('/tag')
    16 def tag():
    17     """  模板标签的使用 """
    18     var = None
    19     a = 2
    20     list_user = [
    21         {'username': '张三', 'age': 32, 'address': '北京'},
    22         {'username': '李四', 'age': 22},
    23         {'username': '王五', 'age': 32, 'address': '北京'},
    24         {'username': '王文', 'age': 22}
    25     ]
    26     # list_user = []
    27     return render_template('tag.html',
    28                            var=var,
    29                            a=a,
    30                            list_user=list_user)
     1 
     2 
     3 DOCTYPE html>
     4 <html lang="en">
     5 <head>
     6     <meta charset="UTF-8">
     7     <title>模板标签的使用title>
     8     <style type="text/css">
     9         .odd {
    10             background-color: #f00;
    11             color: #fff;
    12         }
    13         .even {
    14             background-color: aqua;
    15             color: #fff;
    16         }
    17     style>
    18 head>
    19 <body>
    20     <h3>for 循环 dicth3>
    21     {% for user in list_user %}
    22         <p class="{{ loop.cycle('odd','even') }}">
    23         第{{ loop.index }}个用户,总共{{ loop.length }}个:<br/>
    24         {% for key, value in user.items() %}
    25             {{ key }}: {{ value }}
    26         {% endfor %}
    27         p>
    28     {% else %}
    29         <p>用户信息为空p>
    30     {% endfor %}
    31 
    32     <h3>for 循环 dict -breakh3>
    33     {% for user in list_user -%}
    34         <p class="{{ loop.cycle('odd','even') }}">
    35         第{{ loop.index }}个用户,总共{{ loop.length }}个:<br/>
    36         {% for key, value in user.items() -%}
    37             {% if loop.index > 2 -%}    
    38                 {% break -%}
    39             {% endif -%}
    40             {{ key }}: {{ value }}
    41         {% endfor -%}
    42         p>
    43     {% else %}
    44         <p>用户信息为空p>
    45     {% endfor %}
    46 body>
    47 html>

     

    同时模板标签还支持添加注释,注释分为两种:

    1)会在浏览器html源码中显示:  

    2)不会在浏览器html源码中显示: {# 注释内容 #} 

     

    去除HTML源码中多余的空白可以提高性能,不能忽视!去除方式,在块的开始或结束放置一个减号(-),不能有空格

     

     

     

     

    • 设置变量,进行赋值操作:先设置,后使用,可以通过import导入;
    
    {% set key,value = (1,2) %}

    建议设置变量的时候,和with代码块结合使用,实现块级作用域。这个变量只在with这个代码块中有效果。

     

    思考:如下内容如何显示?

    {{}}  {% %}

    1)视为字符串: {{ ‘{{}} {% %}’ }}  

    2)使用raw标签进行转义,使得特殊符号正常显示

     

    3.2.模板语法_过滤器

    思考:下面的场景如何实现?

    什么是过滤器?

    • 过滤器:定义好规则,修改变量(如:格式化显示)
    • 过滤器的使用格式,用管道符号(|)分割,如:{{ name|striptags }},striptags就是过滤器,将name变量传递给striptags处理最后返回处理后的结果
    • 可以链式调用:{{ name|striptags|title }},name传递给多个过滤器
    • 可以用园括号传递可选参数{{ list|join(',')}}

     

    过滤器的使用

    方式一:用管道符( | )

    {{ value|safe }}

     

    方式二:使用便签{% filter 过滤器 %} {% endfilter %}

    {% filter upper %}
        This text becomes uppercase
    {% endfilter %}
    
    过滤器upper对字符串This text。。。进行格式化显示

     

    内置的过滤器

    • 求绝对值abs:{{ value|abs }}
    • 默认值显示:default(value,default_value='',boolean=False)
      • {{ value|default('默认值')}}:如果value变量没有定义,传递过来,default过滤器处理value返回“默认值” 
      • {{ value|d('默认值') }}:上面的简写

    •  html转义escapee:{{ value | escape }} 或 {{ value | e }}

    👆由于浏览器一般默认对html自动转义成字符串输出,所以我们使用{% autoescape fale %}将其关闭。这时调试,过滤器e的作用就直观了。

     

    自定义过滤器

    方式一:使用装饰器@app.template_filter对自定义装饰器进行注册;推荐使用这个

    • 示例reverse:自定义装饰器名称
    • 示例reverse_filter函数:自定义装饰器的逻辑方法,需要传递被修改的变量s
    @app.template_filter('reverse')
    def reverse_filter(s):
        return s[::-1]

     

    方式二:调用函数app.jinja_env.filters对自定义装饰器进行注册

    • 示例reverse:自定义装饰器名称
    • 示例reverse_filter函数:自定义装饰器的逻辑方法,需要传递被修改的变量s
    def reverse_filter(s):
        return s[::-1]
    app.jinja_env.filters['reverse'] = reverse_filter

    3.3.模板语法_模板全局函数

    思考:如何在模板中实现range函数的效果?

    全局函数可在模板中直接使用

    1 <ul>
    2 {% for i in range(10) %}
    3   <li>{{ i }}li>
    4 {% endfor %}
    5 ul>

    模板提供的全局函数有:

    • range([start],stop[,step]):和python的range用法基本一致
    • dict(**items):转换成字典形式
    • cycler(*items):常用于css类名的循环。

    应用场景简单说明:声明变量class_name,通过cycler()赋值给这个变量。标签的class属性值等于这个变量,通过next()获取下一个。使用css查找class并对其样式修改。

    • joiner(sep= ','):可用于字符串拼接;相当于python里面join的语法
    • url_for():URL解析函数(如:静态文件地址解析、链接跳转地址解析)

    静态文件地址解析;使用link引用外部css文件时,需要加上rel=stylesheet;rel="stylesheet" 描述了当前页面与href所指定文档的关系.即说明的是:href连接的文档是一个样式表。

    链接跳转地址解析

    4.Flask模板中的宏

    什么是宏?

    把常用功能抽取出来,实现可重用;简单理解:宏≈函数;宏可以写在单独的html文件中。

     

    4.1.模板中的宏

    1)定义宏{% macro %}像书写函数一样定义宏,示例解析

    • 示例代码定义了一个input的宏
    • 调用input宏的时候,需要传入name、value、type、size;其中name是必传参数,value、type和size都有默认值,不传时使用默认值
    • 返回的内容为👇,调用input宏传递的参数,赋值给返回内容{{变量}}。其中value还经过了过滤器e进行了html转义。
    <input type="{{ type }}" name="{{ name }}" value"{{ value|e }}" size="{{ size }}">
    调用宏返回的内容

    2)使用宏{{ 宏名() }}像调用函数一样调用。

    3)完整示例

    4.2.文件中的宏

    可以把写在html文件中的宏,当成python的一个模块进行使用

    1)导入宏,跟python使用模块方法一样。

    • {% import 'forms.html' as forms %}:导入宏文件forms.html,并重命名forms
    • {%  from 'forms2.html' import input2 %}:导入宏文件forms2.html中的input宏

    2)使用宏

    • {{ forms.input('username' )}}

    • {{ input2('password' )}}

    3)完成示例

    5.Flask模板的继承

    思考:为什么要对模板进行抽象和继承?模板的抽象继承,和python的面向对象是一样的。

    思考:如下场景怎样设计(易维护、可扩展)?

    • 每个页面都引用了公共的头部,js,css
    • 有几个页面结构和内容及其相似(如:导航菜单)

     

    5.1.继承实现

    设定:有一个公用模板页面base.html。其他模板页面基于base.html修改自己页面的header、body等部分信息。其他的使用公共模板页面base.html

      1)在通用模板上,通过 {% block 部分名称%}{% endblock %} 将可变的部分圈出来;图示步骤一,使用block将可变的部分圈出来,命名为content。

      2)新模板页面,通过 {% extends "父模板" %} 继承通用模板;虽然新模板.html只有一行代码 {% extends "父模板" %} ,但是新模板目前是和父类模板展示效果一样的。

      3)新模板页面这时候要定制开发了,通过 {% block 选择要修改的可变部分名称%}你修改的新代码块{% endblock %} ,完成修改后,新模板页面展示你修改后的效果

      4)当你想保留可变部分,并且基本可变部分原先内容再添加新内容时,通过 {{ super() }}后面接你新的内容 ,既保留了可变部分又添加了你新增的部分

    示例代码:

     

    思考:只有部分页面使用到的导航条怎么设计?

    • 按照继承的手法,我们可以将这些部分页面圈起来,继承的时候,然后重写他内容为空。但是这个方法是不是很low
    • 使用模板的包含语法进行实现才是最有解决方案

    5.2.模板继承的包含语法

    • include “sidebar.html” ignore missing:sidebar.html不存在,不会报错
    • include “sidebar.html” ignore missingwith context:将模板的上下文对象传递到sidebar.html文件中
    • include “sidebar.html” ignore missing without context:将模板的上下文对象不传递到sidebar.html文件中
    • include without context的简单示例

     

    5.3.继承与包含的区别

    6.消息闪现

     

    什么是消息闪现

    思考:要在如下场景下给予操作提示,怎么实现?

    • 用户登录成功,提示:欢迎回来
    • 用户发布问题成功,提示:发帖成功

     

    消息闪现

    第一步:在视图中产生一个消息(提示/警告/错误)

    flash(msg_content,msg_type)
    
    #参数msg_content:消息内容
    #参数msg_type:消息类型
    View Code

    第二步:在模板中展示消息

    get_flashed_messages(category_filter=["error"])
    
    #参数category_filter:对产生的消息按类别查询
    #返回的是一个可迭代的对象
    View Code

    使用for循环获取get_flashed_messages返回的对象内容:category,message

    实例:

     get_flashed_messages参数说明

    • 参数不是必填的可以不传
    • with_categories=true:代表我们启用消息分类,之后针对消息类型,可以进行相关的过滤操作
    • category_filter=['error']:这里面参数是list,筛选参数msg_type:消息类型

    code

     1 # app.py
     2 
     3 from flask import Flask, render_template, flash, redirect, request
     4 
     5 app = Flask(__name__)
     6 # session的安全机制,使用flash时需要设置该随机串
     7 app.secret_key = 'secret_keyabcdes334'
     8 
     9 # 用户登录之后,跳转到个人中心,在个人中心页面,展示一个提示:登录成功
    10 
    11 
    12 @app.route('/login', methods=['GET', 'POST'])
    13 def login():
    14     """  用户登录 """
    15     if request.method == 'POST':
    16         print('处理了登录的逻辑')
    17         flash('登录成功', 'success')
    18         flash('欢迎回来', 'success')
    19         flash('错误提示', 'error')
    20         return redirect('/mine')
    21     return render_template('login.html')
    22 
    23 
    24 @app.route('/mine')
    25 def mine():
    26     """  个人中心 """
    27     return render_template('mine.html')
     1 
     2 
     3 DOCTYPE html>
     4 <html lang="en">
     5 <head>
     6     <meta charset="UTF-8">
     7     <title>用户登录title>
     8 head>
     9 <body>
    10     <h3>用户登录h3>
    11     <form action="{{ url_for('login') }}" method="post">
    12         <div>
    13             <input type="text" name="username" value="admin">
    14         div>
    15         <div>
    16             <input type="password" name="password">
    17         div>
    18         <div>
    19             <button type="submit">登录button>
    20         div>
    21     form>
    22 body>
    23 html>
     1 
     2 
     3 DOCTYPE html>
     4 <html lang="en">
     5 <head>
     6     <meta charset="UTF-8">
     7     <title>个人中心title>
     8     <style type="text/css">
     9         .success {
    10             color: #0f0;
    11         }
    12         .error {
    13             color: #f00;
    14         }
    15     style>
    16 head>
    17 <body>
    18     <h3>个人中心h3>
    19     {% for category, message in get_flashed_messages(with_categories=true, category_filter=['error']) %}
    20     <p class="{{ category }}">
    21         {{ category }} -{{ message }}
    22     p>
    23     {% endfor %}
    24 body>
    25 html>

     

    6.重点掌握

    • 模板语法的使用(变量、标签、过滤器)
    • 模板的继承、包含、引用
    • 模板中的宏定义和使用
    • 消息闪现

     


    __EOF__

  • 本文作者: 葛老头
  • 本文链接: https://www.cnblogs.com/gltou/p/16828437.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    设计模式-原型模式
    秒杀系统设计
    直流有刷电机开环调速基于STM32F302R8+X-NUCLEO-IHM07M1(一)
    告诉你如何从keil工程知道使用了多少RAM和ROM空间
    解决FeignClient被FallBack后无错误日志打印问题
    uniapp h5中疯狂报错Invalid Host/Origin header
    常见生产环境问题及排查方法
    都3年测试经验了,用例设计还不知道状态迁移法?
    编程语言排行榜
    记录一次时序数据库的实战测试
  • 原文地址:https://www.cnblogs.com/gltou/p/16828437.html