python学习笔记第13章:web开发之sanic框架

sanic官方中文教程https://sanicframework.org/zh/guide/

教程不涉及模板等内容,所以用前后端分离架构,后端只返回数据,不渲染模板

架构如下

使用两个服务器软件,sanic自带的软件和nginx

sanic服务器软件,运行在5000端口,只接收本机请求,只代理后端

nginx运行在80端口,接收外部请求,同时代理前端和后端,将指定请求转发到后端所在的5000端口,改动部分如下,其他都是默认配置,没有改动

点击查看代码
server {
        listen       80;                
        server_name  localhost;


        access_log  logs/host.access.log  main;
		#前端,页面
        location / {                                             #访问根路径,打开前端主页,不进行转发
            root   C:/Users/zx/Documents/pyProject/web/my_app;   #设置前端文件的主目录
            index  index.html index.htm;
        }
		#后端,接口
		location /api/{                            #访问路径带api时,进行转发,如http://127.0.0.1/api/login, 会被转发给后端                                                                    
                proxy_pass http://127.0.0.1:5000/;                #转发给本机5000端口
				proxy_set_header Host $host;

        }

1.示例文件

为便于展示所有文件放在同一个目录里

api                           #存放目录

----server.py           #主程序

----auth.py             #指定页面需要登录才能执行操作

----login.py            #用户登录验证,jwt认证

----query.py          #自定义文件,使用蓝图注册到主程序

前端文件

----index.html       #登录页

----query.html      #功能查询页

 

1.1 login.py,用户登录验证,创建蓝图

import jwt                                  #注意,安装命令是pip install pyjwt,而不是pip install jwt
from sanic import text 
from sanic import Blueprint                 #导入蓝图类
login = Blueprint("login")                  #实例化蓝图,login是蓝图名称,引号里的内容随便填,


@login.post("/login")
async def do_login(request):                #sanic的函数都要带request参数,用于获取前端传来的数据
    user = request.form.get('user')         #获取表单中id为user的值
    passwd = request.form.get('passwd')
    if user== 'inflow' and passwd == 'inflow':    
        token = jwt.encode({}, request.app.config.SECRET)
        return text(token)                                       #验证通过返回token
    return text('fail')
from sanic import Blueprint login = Blueprint("login",url_prefix=‘/abc’)        #创建一个蓝图实例,url__prefix参数可选,用于指定路径 如以上代码指定该参数,访问login的路径就变成了/abc/login,不添加时为/login 该文件用于验证用户登录,验证成功返回一个token,在受保护的路由上,只有验证成功才能访问数据  

1.2 server.py  主程序,注册蓝图,启动程序,设置配置项,父目录

from sanic import Sanic
from  login import login            #从login.py文件导入创建的login蓝图对象
from  query import query

#0.配置
app = Sanic(__name__)
app.config.SECRET = "KEEP_IT_SECRET_KEEP_IT_SAFE"   #设置秘钥,用于jwt认证,config配置项一般放在一个本地文件中,从文件读取
app.config.SERVER_NAME = "/api"                     #设置顶层路径,所有的路由路径都在api下面,比如/api/login

#1.蓝图注册
app.blueprint(login)               #注册login蓝图,路径为http://127.0.0.1/api/login
app.blueprint(query)


#3.执行
if __name__ =='__main__':
    app.run(port=5000, debug=True, access_log=False, workers=4)        
    #1.端口运行在5000,2.开启debug模式,有修改时自动加载,不用重启程序,3.关闭访问日志,提高速度,日志可在nginx中设置。4.开启4个线程
    #命令行执行方式,进入server.py 所在目录sanic server.app --host=0.0.0.0 --port=5000 --workers=4  --debug=True --access_log=False
    #和flask一样,host设置为0000时,从其他计算机也能访问,设置为127,只有本机能访问,因为我们使用nginx做为代理,所以可以用127

1.3 auth.py 路由保护

点击查看代码
from functools import wraps
import jwt
from sanic import text
#完全照搬官方教程,一字未改

def check_token(request):
    if not request.token:
        return False

    try:
        jwt.decode(
            request.token, request.app.config.SECRET, algorithms=["HS256"]
        )
    except jwt.exceptions.InvalidTokenError:
        return False
    else:
        return True


def protected(wrapped):
    def decorator(f):
        @wraps(f)
        async def decorated_function(request, *args, **kwargs):
            is_authenticated = check_token(request)

            if is_authenticated:
                response = await f(request, *args, **kwargs)
                return response
            else:
                return text("You are unauthorized.", 401)

        return decorated_function

    return decorator(wrapped)

#这个文件照搬的官方脚本,没做任何改动,就不说了

用法:需要保护的路由,导入auth模块的protected函数

from auth  import protected   @protected       #在函数上加一个装饰器就可以了,示例见1.4  

1.4 query.py

from sanic import Blueprint, json
from auth import protected         #导入protected
import tools, add_user_right       

# 0.数据定义
query = Blueprint('query')       


# 一.相关函数
@query.post("/query")              
@protected                         #添加了protected,只有1.1 login.py文件通过登录验证才会执行下面的函数,否则返回login.py中定义的401
async def query_data(request):
    name = request.form.get('name')
    data = {}
    if  request.form.get('radio1') == 'user_info':      
           data = tools.find_sysuser(name)

    return json({"h": data})                    #返回json类型的数据

1.5index.html  用户登录表单

点击查看代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

<title>登录</title>
      <link rel="stylesheet" type="text/css" href="../css/style.css">
    <link href="../css/bootstrap.min.css" rel="stylesheet">
    <link href="../css/main.css" rel="stylesheet">
  </head>
<body>
<link rel="stylesheet" type="text/css" href="css/style.css">
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
      <div class="container">
        <div class="navbar-header">
          <a class="navbar-brand" href="/">首页</a>
            <a class="navbar-brand" href="/templates/query.html">数据查询</a>
            <a class="navbar-brand" href="/templates/export.html">数据导出</a>
        </div>
      </div>
</div>
<br>
    <form action="/api/login" method="post" class="form" role="form">
    <div class="form-group required"><label class="form-control-label" for="user">账号</label>
        <input class="form-control" id="user" name="user" required type="text" value="">
    </div>

    <div class="form-group required"><label class="form-control-label" for="passwd">密码</label>
        <input class="form-control" id="passwd" name="passwd" required type="password" value="">
        </div>
            <input class="btn btn-primary btn-md" id="submit" name="submit" type="submit" value="登录">
    </form>           <!-- 表单 -->


</body>

action中定义了表单的提交路径/api/login .点击提交后访问http://127.0.0.1/api/lgoin , 路径含有api会转发到后端

1.6 query.html

点击查看代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title>查询</title>
  <link rel="stylesheet" type="text/css" href="../css/style.css">
  <link href="../css/bootstrap.min.css" rel="stylesheet">
  <link href="../css/main.css" rel="stylesheet">

  <style>
    body {
      font-size: 14px;
    }

    label {
      display: inline-block;
      width: 8em;
      margin-left: 0.3em;
      margin-right: 0.3em;
    }

    input {
      margin-top: 0.3em;
      margin-bottom: 0.3em;
    }

    .tipmsg {
      font-size: 14px;
      color: #f00;
    }
  </style>

</head>

<body>
  <div class="navbar navbar-inverse navbar-fixed-top" role="navigation">
    <div class="container">
      <div class="navbar-header">
        <a class="navbar-brand" href="/">首页</a>
        <a class="navbar-brand" href="/templates/query.html">数据查询</a>
        <a class="navbar-brand" href="/templates/export.html">数据导出</a>
      </div>
    </div>
  </div>
  <div class="container">
    <br />
    <div>

    </div>


    <form id="query_form" name="query_form" method="post" class="form" role="form">
      <div class="form-group row">
        <label class="col-form-label  col-lg-2" for="name">姓名</label>
        <div class=" col-lg-10">
          <input class="form-control" id="name" name="name" type="text" value="">
        </div>
      </div>
      <div class="form-group row">
        <label class="col-form-label  col-lg-2" for="phone">手机</label>
        <div class=" col-lg-10">
          <input class="form-control" id="phone" name="phone" type="text" value="">
        </div>
      </div>
      <div class="form-group row">
        <label class="col-form-label  col-lg-2" for="role">角色</label>
        <div class=" col-lg-10">
          <input class="form-control" id="role" name="role" type="text" value="">
        </div>
      </div>
      <div class="form-group row">
        <label class="col-form-label  col-lg-2" for="depart">部门</label>
        <div class=" col-lg-10">
          <input class="form-control" id="depart" name="depart" type="text" value="">
        </div>
      </div>

      <div>
        用户信息查询<input type="radio"  name="radio1" value="user_info" />
        角色部门查询<input type="radio" name="radio1" value="role_depart" />
        测试<input type="radio" name="radio1" value="test" />
        添加角色<input type="radio" name="radio1" value="role_add" />
        修改角色<input type="radio" name="radio1" value="role_mod" />
        删除角色<input type="radio" name="radio1" value="role_del" />
        添加用户<input type="radio" name="radio1" value="user_add" />
      </div>
      <div class="form-group row">
        <div class="offset-lg-2col-lg-10">
          <input class="btn btn-primary btn-md" id="query_button" name="query_button" type="button" value="查询"
            onclick="user_query()">
        </div>
      </div>
    </form>
    <!-- 数据 -->
    <div id="con"></div>
  </div>

  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
  <script type=text/javascript>
  function user_query(){
    $.ajax({
         url:'/api/query',
         type:'POST',
         data: $('#query_form').serialize(),
         success:function(data){
               show_data(data.h);
         }
    });
  };
  function show_data(data){
    for(var i=0;i<data.length;i++){
      var addDivDom = document.createElement('div'); // 创建div标签
      var bodyDom = document.body;
      bodyDom.insertBefore(addDivDom, bodyDom.lastChild);  // 将addDivDom添加到body中的最后子节点中。
      addDivDom.innerHTML = i + "." + data[i];     //输出
      //addDivDom.id = 'id';             // div标签添加id
      // addDivDom.style.color = '#fff'; // 书写style样式
      //addDivDom.classList.add('classname'); // 引入css文件中的某个class样式
      
      //$('#con').html(data[i]);
      //$('#con').after(data[i]);
    }
  }
</script>

</body>

</html>

表单提交使用ajax异步加载,提交路径/api/query , 该路由被protectd保护,登录验证通过才能执行查询

 

上一篇:Sanic四:Sanic.run支持的参数


下一篇:Sanic八:Sanic操作cookie